总目录
文章目录
- 总目录
- 一、WPF异常
- 1 未捕获异常
- 2 模拟未捕获异常场景
- 二、处理未捕获异常
- 1 DispatcherUnhandledException 异常捕获
- 2 UnhandledException异常捕获
- 3 UnobservedTaskException异常捕获
- 4 异常捕获的综合使用
- 结语
一、WPF异常
1 未捕获异常
正常情况下,开发过程中都会使用try…catch
在可能会出现异常的地方去捕获和处理异常。然而实际上开发过程中,由于开发疏忽和一些未知原因,程序中会存在未被处理的异常,当程序运行到此,可能会导致程序崩溃的情况,这样会大大的降低用户的使用体验。对于这种未发现未处理的异常,称之为未捕获异常(UnhandledException)。
2 模拟未捕获异常场景
下面的案例中,就是模拟一个未捕获的异常场景,点击按钮,抛出异常,不使用try…catch 捕获处理。
private void Button_Click(object sender, RoutedEventArgs e)
{
throw new Exception("一个异常!");
}
这种由于未捕获异常导致的程序崩溃,从而影响使用者的正常操作,是令人很不友好和反感的。此类异常如果需要追溯就必须去查看Windows的事件日志
二、处理未捕获异常
我们虽然不能完全杜绝未捕获异常的产生,但是当其出现的时候,我们应当予以处理,做到尽量不影响使用者的操作。
在WPF应用程序中,各类未处理异常及其处理方式如下:
异常种类 | 处理方式 | 案例说明 |
---|---|---|
UI线程抛的异常 | 使用Application.Current.DispatcherUnhandledException 事件处理 | 例如点击了用户界面上面的某个控件,然后执行某行代码的时候,遇到了异常; |
非UI线程抛的异常 | 使用AppDomain.CurrentDomain.UnhandledException事件处理 | 例如在一个多线程的程序里面,工作线程的代码遇到了异常。 |
Task线程抛的异常 | 使用TaskScheduler.UnobservedTaskException事件处理 | 例如在一个多线程的程序里面,工作线程的代码遇到了异常。 |
1 DispatcherUnhandledException 异常捕获
- DispatcherUnhandledException 用于捕获UI线程的异常,对于多线程Thread 和Task 异常不会捕获。
- 该事件中可以通过设置 e.Handle=true ,表明该异常已被处理,不会造成程序崩溃和退出
具体验证代码如下:
上面测试结果:
- UI线程异常【可以捕获】,通过e.Handle处理,应用程序不会发生崩溃或退出情况
- Thread多线程异常【无法捕获】,会造成应用程序崩溃或退出
- Task多线程异常【无法捕获】,但不会造成程序崩溃或退出
2 UnhandledException异常捕获
- UnhandledException 用于捕获应用程序所有的异常
- 该事件只管捕获,没有什么e.Handle的设置去处理异常
- 若想应用程序捕获到异常后不退出或崩溃需要配合legacyUnhandledExceptionPolicy 配置设置实现(作用类似于e.Handle=true)
如何配置legacyUnhandledExceptionPolicy 呢?只需要在 app.config 文件的 <runtime>
节点中添加如下代码:
<legacyUnhandledExceptionPolicy enabled="1"/>
具体使用案例如下所示:
上面测试结果:
- UI线程异常【可以捕获】,但是应用程序仍会发生崩溃或退出情况
- Thread多线程异常【可以捕获】,应用程序不会崩溃或退出
- Task多线程异常【无法捕获】,也不会造成程序崩溃或退出
3 UnobservedTaskException异常捕获
- UnobservedTaskException 专用于捕获Task 多线程异常
上面测试结果:
- UI线程异常【无法捕获】,应用程序发生崩溃或退出
- Thread多线程异常【无法捕获】,应用程序会崩溃或退出
- Task多线程异常【可以捕获】,由于Task多线程的异常本身也不会让应用程序崩溃或退出,因此我们只需在捕获的事件内处理异常即可
UnobservedTaskException 事件执行的时机?
通过上面的案例中,我们发现在Task中发生异常了以后,并不会马上执行UnobservedTaskException 事件内的代码,而是会等一会儿才执行? 这是因为Task异常只有在垃圾回收的时候,才会推送到该事件内进行处理。
4 异常捕获的综合使用
- 在xaml中
<StackPanel>
<Button Content="测试UI线程异常" Width="300" Height="50" Click="Button_Click" Margin="10"></Button>
<Button Content="测试Thread线程异常" Width="300" Height="50" Click="Button_Click_1" Margin="10"></Button>
<Button Content="测试Task线程异常" Width="300" Height="50" Click="Button_Click_2" Margin="10"></Button>
</StackPanel>
- 在代码中
private void Button_Click(object sender, RoutedEventArgs e)
{
throw new Exception("UI线程异常[01]!");
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
new Thread(new ThreadStart(()=>
{
throw new Exception("Thread多线程异常[02]");
})).Start();
}
private void Button_Click_2(object sender, RoutedEventArgs e)
{
Task.Run(()=>
{
throw new Exception("Task多线程异常[03]");
});
}
- 在App.xaml.cs的代码中
public partial class App : Application
{
public App()
{
//当应用程序引发但未处理异常时出现,UI线程的异常,无法捕获多线程异常
Application.Current.DispatcherUnhandledException += Current_DispatcherUnhandledException;
//当某个异常未被捕获时出现
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
//未被观察到的Task多线程异常
TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
}
private void Current_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
{
e.Handled = true;
MessageBox.Show($"Current_DispatcherUnhandledException:" + e.Exception.Message);
}
private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
MessageBox.Show($"CurrentDomain_UnhandledException:" + (e.ExceptionObject as Exception).Message);
}
private void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
{
MessageBox.Show($"TaskScheduler_UnobservedTaskException:" + e.Exception.Message);
}
}
结语
以上就是本文的内容,希望以上内容可以帮助到您,如文中有不对之处,还请批评指正。
参考资料:
WPF异常处理
TaskScheduler.UnobservedTaskException 事件