在 WPF 程序中,通常可以通过 Application.DispatcherUnhandledException 或 AppDomain.UnhandledException 事件来处理全局 未处理异常,其中前者是由 WPF 框架提供的,后者是由 .NET Framework 提供的,后者能够捕获更多的未处理异常。对于 Task 中的未处理异常,这两种事件都不会触发,仅能通过 TaskScheduler.UnobservedTaskException 事件来捕获。另外,还有个 AppDomain.FirstChanceException 事件,每个异常都会引发该事件,即使该异常已被 try...catch 处理,此事件不在本文的讨论范围内。
Application.DispatcherUnhandledException 事件 能够捕获 UI 线程抛出的未处理异常可通过事件参数 e.Handled = true 来阻止程序崩溃AppDomain.UnhandledException 事件 能捕获 所有线程(Task 除外) 抛出的未处理异常默认情况无法阻止程序崩溃(可通过 legacyUnhandledExceptionPolicy 配置异常策略 )TaskScheduler.UnobservedTaskException 仅能捕获 Task 中抛出的未处理异常事件的触发有延时,依赖垃圾回收对于一个 UI 线程抛出的未处理异常,其会先触发 DispatcherUnhandledException 事件,如果该事件处理方法中未标记 e.Handled 为 true,则会进一步触发 UnhandledException 事件。
此类未捕获异常仅会触发 UnhandledException 事件,并且事件参数中并未提供类似 e.Handled 的方法来阻止程序崩溃,通常仅在该事件处理方法中添加日志记录或用户提示。在 .NET 2.0 及以前的版本,此类未处理异常是不会引起程序崩溃的,我们也可以通过配置来开启旧的异常处理策略,在 App.Config 中添加如下配置:
<configuration> <runtime> <legacyUnhandledExceptionPolicy enabled="1"/> </runtime> </configuration>如上的代码不会触发 UnhandledException 事件,也不会引起程序奔溃。如果想从全局捕获此类未处理异常,可注册 TaskScheduler.UnobservedTaskException 事件。
protected override void OnStartup(StartupEventArgs e) { TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException; base.OnStartup(e); } private void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e) { MessageBox.Show($"TaskScheduler_UnobservedTaskException: {e.Exception}"); e.SetObserved(); // 标识异常已被观察,不会传给系统,避免崩溃 }此事件并非在抛出异常后立即触发,其依赖于垃圾回收,在某次垃圾收集过程,从 Finalizer 线程里触发并执行。可通过如下方式来强制垃圾回收,及时触发事件(实际工程中避免这些操作,会有性能问题)。
private void Button_Click(object sender, RoutedEventArgs e) { var task = Task.Run(() => throw new NotImplementedException()); ((IAsyncResult)task).AsyncWaitHandle.WaitOne(); task = null; GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); }另外,还可将 Task 中的异常转到调度线程中,从而引发 UnhandledException 事件,Task.Result、Task.Wait() 等都可实现此效果。
private void Button_Click(object sender, RoutedEventArgs e) { var task = Task.Run(() => throw new NotImplementedException()); task.Wait(); }在 托管代码 中调用 非托管 接口,部分未处理异常是无法接住的,会直接引起程序崩溃。如下所示,C++ 中实现了 Add(int x, int y) 方法,在 C# 中调用之,前面的未处理异常事件均不会触发,程序会直接崩溃。
extern "C" _declspec(dllexport) int Add(int x, int y) { char *p = nullptr; *p = '1'; // 此处会抛异常 return x + y; } [DllImport("MyDll")] public static extern int Add(int x, int y); private void ButtonBase_OnClick(object sender, RoutedEventArgs e) { Add(1, 2); }结合前面 Task 内部异常的特性,可以将调用代码放在 Task 中,以避免程序崩溃。Task 是个神奇的东西,还需要进一步深入学习。
private void ButtonBase_OnClick(object sender, RoutedEventArgs e) { Task.Run(() => Add(1, 2)); }原文地址:https://blog.csdn.net/Iron_Ye/article/details/82913025