什么是异常?
在C#中,异常是在程序执行过程中发生的特殊情况,例如尝试除以零、访问不存在的文件、网络连接中断等。这些情况会中断程序的正常流程。
当C#程序中发生这种特殊情况时,会创建一个异常对象并将其抛出。这个异常对象包含了关于异常的详细信息,如异常类型和异常发生时的程序状态。
异常处理是一个重要的编程概念,它允许程序员在异常发生时采取适当的行动,而不是让程序崩溃。在C#中,我们使用try,catch和finally关键字来处理异常。
比如,下面的代码用0除一个数时,会出现一个异常:
internal class Program { static void Main(string[] args) { int x = 10, y = 0; x /= y; } }
try语句
try语句用来指明为避免出现异常而被保护的代码段,并在发生异常时提供代码处理异常。try语句由3个部分组成,如下图所示:
处理异常
上面除以0会导致一个异常的程序,可以进行如下改写,进行异常处理:
internal class Program { static void Main(string[] args) { int x = 10; try { int y = 0; x /= y; } catch { Console.WriteLine("Handling all exceptions"); } } }
异常被捕获:
异常类
在C#中,所有的异常都是派生自System.Exception类的。System.Exception类是所有异常的基类,它提供了一些基本的功能,如返回错误消息和记录异常发生的堆栈跟踪。
C#提供了一些内置的异常类,用于表示常见的异常情况。例如:
- System.NullReferenceException:当你试图访问一个null对象的成员时,会抛出这个异常。
- System.DivideByZeroException:当你试图除以零时,会抛出这个异常。
- System.IndexOutOfRangeException:当你试图访问数组或集合的无效索引时,会抛出这个异常。
- System.IO.FileNotFoundException:当试图打开的文件不存在时,会抛出这个异常。
当一个异常发生时,CLR会创建该类型的异常对象,并寻找适当的catch子句处理它。
所有异常类从根本上派生自System.Exception类,异常继承层次的一个部分如下所示:
catch子句
catch子句处理异常。它有3种形式,允许不同级别的处理,如下图所示:
- 一般catch子句:这种形式的catch子句可以捕获任何类型的异常。它不指定异常类型,所以它会捕获try块中抛出的所有异常。
- 特定catch子句:这种形式的catch子句只捕获指定类型的异常。如果try块中抛出的异常类型与catch子句中指定的类型匹配,那么就会执行这个catch子句。
- 带对象的特定catch子句:这种形式的catch子句不仅指定了异常类型,还定义了一个异常对象。这个异常对象可以用来访问关于异常的更多信息,如错误消息和堆栈跟踪。
将开头的例子,修改为使用带对象的特定catch子句,如下所示:
internal class Program { static void Main(string[] args) { int x = 10; try { int y = 0; x /= y; } catch(DivideByZeroException e) { Console.WriteLine($"Message:{e.Message}"); Console.WriteLine($"Source: {e.Source}"); Console.WriteLine($"Stack: {e.StackTrace}"); } } }
输出结果如下所示:
抛出异常
可以使用throw语句使代码显式地引发一个异常。throw语句的语法如下:
throw ExceptionObject;
好了,以上就是C#中关于异常处理的基础知识,现在我们结合WPF中的例子说明在WPF中如何进行异常处理的。
WPF中的异常处理
现在来看看ExceptionHandlingSecondaryUIThread这个例子,项目结构如下图所示:
先来看一下这个项目的运行效果:
这个例子介绍了WPF中如何处理在辅助UI线程中发生的异常。
现在来看看是如何处理的。
StartSecondaryUIThreadButton
按钮点击事件处理程序:
private void startSecondaryUIThreadButton_Click(object sender, RoutedEventArgs e) { // Creates and starts a secondary thread in a single threaded apartment (STA) var thread = new Thread(MethodRunningOnSecondaryUIThread); thread.SetApartmentState(ApartmentState.STA); thread.IsBackground = true; thread.Start(); }
这段代码的主要目的是创建并启动一个新的辅助UI线程。
thread.SetApartmentState(ApartmentState.STA);
这行代码设置了线程的公寓状态为STA(Single-Threaded Apartment)。在WPF中,所有的UI线程都必须是STA线程,因为UI元素不是线程安全的。
Single-Threaded Apartment(STA)介绍
在WPF(Windows Presentation Foundation)中,Single-Threaded Apartment(STA)是指一个线程模型,其中每个线程都维护自己的消息队列,并且所有的UI操作都在这个线程上进行。
在STA模型中,每个线程都有自己的内存空间,这意味着线程之间的数据不会共享,从而避免了多线程编程中的许多并发问题。这对于UI编程来说非常重要,因为UI元素通常不是线程安全的,所以所有的UI操作都必须在同一个线程上进行。
在WPF中,主UI线程默认就是一个STA线程。此外,你也可以创建其他的STA线程,但是每个STA线程都只能操作它自己创建的UI元素。
MethodRunningOnSecondaryUIThread
方法如下所示:
// THIS METHOD RUNS ON A SECONDARY UI THREAD (THREAD WITH A DISPATCHER) private void MethodRunningOnSecondaryUIThread() { var secondaryUiThreadId = Thread.CurrentThread.ManagedThreadId; try { // On secondary thread, show a new Window before starting a new Dispatcher // ie turn secondary thread into a UI thread var window = new SecondaryUIThreadWindow(); window.Show(); Dispatcher.Run(); } catch (Exception ex) { // Dispatch the exception back to the main ui thread and reraise it Application.Current.Dispatcher.Invoke( DispatcherPriority.Send, (DispatcherOperationCallback) delegate { // THIS CODE RUNS BACK ON THE MAIN UI THREAD string msg = $"Exception forwarded from secondary UI thread {secondaryUiThreadId}."; throw new Exception(msg, ex); } , null); // NOTE - Application execution will only continue from this point // onwards if the exception was handled on the main UI thread // by Application.DispatcherUnhandledException } }
在try语句块中创建了SecondaryUIThreadWindow。
SecondaryUIThreadWindow上的按钮的点击事件处理程序如下所示:
private void raiseExceptionOnSecondaryUIThreadButton_Click(object sender, RoutedEventArgs e) { // Raise an exception on the secondary UI thread string msg = $"Exception raised on secondary UI thread {Dispatcher.Thread.ManagedThreadId}."; throw new Exception(msg); }
抛出了一个异常。
这个异常被MethodRunningOnSecondaryUIThread
方法中的catch子句段捕获:
Application.Current.Dispatcher.Invoke( DispatcherPriority.Send, (DispatcherOperationCallback) delegate { // THIS CODE RUNS BACK ON THE MAIN UI THREAD string msg = $"Exception forwarded from secondary UI thread {secondaryUiThreadId}."; throw new Exception(msg, ex); } , null);
这段代码的主要目的是在辅助UI线程上捕获异常,并将异常转发到主UI线程上进行处理。
在app.xaml中
DispatcherUnhandledException="App_DispatcherUnhandledException"
这行代码是在WPF应用程序中设置全局未处理异常的处理器。
当应用程序的主调度器捕获到未处理的异常时,App_DispatcherUnhandledException方法会被调用来处理这个异常。
这是一种处理全局未处理异常的方式,可以防止应用程序因为未处理的异常而崩溃。在App_DispatcherUnhandledException方法中,你可以记录异常信息,显示错误消息,或者决定是否让应用程序继续运行。
private void App_DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e) { // Display exception message var sb = new StringBuilder(); sb.AppendFormat("{0}\n", e.Exception.InnerException.Message); sb.AppendFormat("{0}\n", e.Exception.Message); sb.AppendFormat("Exception handled on main UI thread {0}.", e.Dispatcher.Thread.ManagedThreadId); MessageBox.Show(sb.ToString()); // Keep application running in the face of this exception e.Handled = true; }
这样就完成了在WPF中的辅助UI线程的异常处理。
还有一个例子是ExceptionHandlingSecondaryWorkerThread说明在WPF中如何处理在辅助工作线程(Secondary Worker Thread)中发生的异常,与这个例子类似。
参考
1、《C#图解教程》
2、[WPF-Samples/Application Management/ExceptionHandlingSecondaryUIThread at main · microsoft/WPF-Samples (github.com)]