TAP全局异常处理程序

Sam*_*Sam 18 .net c# exception task-parallel-library async-await

此代码抛出异常.是否可以定义将捕获它的应用程序全局处理程序?

string x = await DoSomethingAsync();
Run Code Online (Sandbox Code Playgroud)

使用.net 4.5/WPF

nos*_*tio 21

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

了解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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

一旦ApplicationException抛出,它就不会被观察到.既TaskScheduler_UnobservedTaskException没有CurrentDomain_UnhandledException被调用也没有被调用._task在等待或等待对象之前,异常保持休眠状态.在上面的示例中,它永远不会被观察到,因此TaskScheduler_UnobservedTaskException仅在任务被垃圾收集时才会被调用.然后吞下这个例外.

AppDomain.CurrentDomain.UnhandledException可以通过ThrowUnobservedTaskExceptionsapp.config以下位置配置来启用事件被触发且应用程序崩溃的旧.NET 4.0行为:

<configuration>
    <runtime>
      <ThrowUnobservedTaskExceptions enabled="true"/>
    </runtime>
</configuration>
Run Code Online (Sandbox Code Playgroud)

以这种方式启用时,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");
}
Run Code Online (Sandbox Code Playgroud)

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