一尘不染

TAP全局异常处理程序

c#

此代码引发异常。是否可以定义一个将捕获它的应用程序全局处理程序?

string x = await DoSomethingAsync();

使用.net 4.5 / WPF


阅读 415

收藏
2020-05-19

共1个答案

一尘不染

如果我理解正确,这实际上是一个 问题。我最初投票决定将其关闭,但现在撤回了我的投票。

重要的是要了解如何将async Task方法内引发的异常传播到方法外。最重要的是,处理任务完成的代码需要 遵守 此类异常。

例如,这是一个简单的WPF应用程序,我在NET 4.5.1上:

using System;
using System.Threading.Tasks;
using System.Windows;

namespace WpfApplication_22369179
{
    public partial class MainWindow : Window
    {
        Task _task;

        public MainWindow()
        {
            InitializeComponent();

            AppDomain.CurrentDomain.UnhandledException +=
                CurrentDomain_UnhandledException;
            TaskScheduler.UnobservedTaskException +=
                TaskScheduler_UnobservedTaskException;

            _task = DoAsync();
        }

        async Task DoAsync()
        {
            await Task.Delay(1000);

            MessageBox.Show("Before throwing...");

            GCAsync(); // fire-and-forget the GC

            throw new ApplicationException("Surprise");
        }

        async void GCAsync()
        {
            await Task.Delay(1000);

            MessageBox.Show("Before GC...");

            // garbage-collect the task without observing its exception 
            _task = null;
            GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
        }

        void TaskScheduler_UnobservedTaskException(object sender,
            UnobservedTaskExceptionEventArgs e)
        {
            MessageBox.Show("TaskScheduler_UnobservedTaskException:" +
                e.Exception.Message);
        }

        void CurrentDomain_UnhandledException(object sender,
            UnhandledExceptionEventArgs e)
        {
            MessageBox.Show("CurrentDomain_UnhandledException:" +
                ((Exception)e.ExceptionObject).Message);
        }
    }
}

一旦ApplicationException被抛出,它就不会被观察到。无论是TaskScheduler_UnobservedTaskExceptionCurrentDomain_UnhandledException被调用。异常保持hibernate状态,直到_task对象被等待或等待。在上面的示例中,它从未被观察到,
因此TaskScheduler_UnobservedTaskException仅当任务被垃圾收集时才会被调用。然后,将 吞噬 此异常。

AppDomain.CurrentDomain.UnhandledException可以通过ThrowUnobservedTaskExceptionsapp.config以下位置进行配置来启用旧的.NET
4.0行为:在该行为中会触发事件并导致应用崩溃。

<configuration>
    <runtime>
      <ThrowUnobservedTaskExceptions enabled="true"/>
    </runtime>
</configuration>

以这种方式启用
,在异常被垃圾收集之后,而不是在引发异常的地方,AppDomain.CurrentDomain.UnhandledException仍将被触发。
__TaskScheduler.UnobservedTaskException

Stephen Toub在他的“ .NET
4.5中的任务异常处理”
博客文章中对此行为进行了描述。有关任务垃圾收集的部分在帖子评论中进行了描述。

方法就是这种情况 async Task 。对于 async void
方法(通常用于事件处理程序)而言,情况大不相同。让我们以这种方式更改代码:

public MainWindow()
{
    InitializeComponent();

    AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
    TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;

    this.Loaded += MainWindow_Loaded;
}

async void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
    await Task.Delay(1000);

    MessageBox.Show("Before throwing...");

    throw new ApplicationException("Surprise");
}

因为async void没有Task可保留的引用(因此以后可能没有观察到或垃圾回收的内容)。在这种情况下,立即在当前同步上下文上引发异常。对于一个WPF应用程序,Dispatcher.UnhandledException会先启用,然后Application.Current.DispatcherUnhandledException,然后AppDomain.CurrentDomain.UnhandledException。最后,如果没有处理这些事件(EventArgs.Handled未设置为true),则无论ThrowUnobservedTaskExceptions设置如何,应用都会崩溃。TaskScheduler.UnobservedTaskException
不是 在这种情况下被解雇,出于同样的原因:没有Task

2020-05-19