WPF UnhandledException


在 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 中抛出的未处理异常
事件的触发有延时,依赖垃圾回收
注册异常事件
protected override void OnStartup(StartupEventArgs e)
{
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
Application.Current.DispatcherUnhandledException += Current_DispatcherUnhandledException;

base.OnStartup(e);
}

private void Current_DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
{
MessageBox.Show($"Current_DispatcherUnhandledException:{e.Exception}");
e.Handled = true; // 标记为 “已处理”,避免异常进一步传递而引起崩溃
}

private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
MessageBox.Show($"CurrentDomain_UnhandledException: {e.ExceptionObject}");
}
 
UI 线程异常
private void Button_Click(object sender, RoutedEventArgs e)
{
throw new InvalidOperationException();
}
 
对于一个 UI 线程抛出的未处理异常,其会先触发 DispatcherUnhandledException 事件,如果该事件处理方法中未标记 e.Handled 为 true,则会进一步触发 UnhandledException 事件。

Thread 异常
private void Button_Click(object sender, RoutedEventArgs e)
{
Thread thread = new Thread(() => { throw new InvalidOperationException(); });
thread.Start();

// ThreadPool.QueueUserWorkItem(state => { throw new InvalidOperationException(); });
}
 
此类未捕获异常仅会触发 UnhandledException 事件,并且事件参数中并未提供类似 e.Handled 的方法来阻止程序崩溃,通常仅在该事件处理方法中添加日志记录或用户提示。在 .NET 2.0 及以前的版本,此类未处理异常是不会引起程序崩溃的,我们也可以通过配置来开启旧的异常处理策略,在 App.Config 中添加如下配置:

<configuration>
<runtime>
<legacyUnhandledExceptionPolicy enabled="1"/>
</runtime>
</configuration>
 
Task 异常
private void Button_Click(object sender, RoutedEventArgs e)
{
var task = new Task(() =>
{
throw new InvalidOperationException();
});
task.Start();

// Task.Run(() => throw new InvalidOperationException());
// Task.Factory.StartNew(() => throw new InvalidOperationException());

// var worker = new BackgroundWorker();
// worker.DoWork += (s, ex) => { throw new InvalidOperationException(); };
// worker.RunWorkerAsync();
}
 
如上的代码不会触发 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));
}
 

————————————————
版权声明:本文为CSDN博主「Iron_Ye」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Iron_Ye/java/article/details/82913025

原文地址:https://www.cnblogs.com/robertyao/p/12857112.html