zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    goweb-goweb基础
    玩转Git
    程序爱好者的常用网站
    高等数学思维导图——6.微分方程
    前端趣玩——超炫的聚光灯效果
    Python课程笔记(四)
    高等数学思维导图——5.多元函数微分法及其应用
    算法很美(四)
    第五章——定积分必记公式
    十大经典排序算法
  • 原文地址:https://www.cnblogs.com/robertyao/p/12857112.html
Copyright © 2011-2022 走看看