TAP全局exception处理程序

这段代码抛出一个exception。 有没有可能定义一个应用程序全局处理程序,将抓住它?

string x = await DoSomethingAsync(); 

使用.net 4.5 / WPF

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

了解async Task方法中引发的exception如何传播到exception之外是很重要的。 最重要的是这种exception需要由处理完成任务的代码来观察

例如,这里是一个简单的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都不被调用。 在等待或等待_task对象之前,exception保持hibernate状态。 在上面的例子中,它永远不会被观察到, 所以TaskScheduler_UnobservedTaskException只有在任务被垃圾收集时才会被调用 。 那么这个例外将被吞噬

旧的.NET 4.0行为, AppDomain.CurrentDomain.UnhandledException事件被触发,应用程序崩溃,可以通过在app.configconfigurationThrowUnobservedTaskExceptions来启用:

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

当以这种方式启用时,当exception获得垃圾收集时, AppDomain.CurrentDomain.UnhandledException仍然会 TaskScheduler.UnobservedTaskException 之后被触发,而不是抛出。

Stephen Toub在他的“.NET 4.5中的任务exception处理”博客文章中描述了这种行为。 关于任务垃圾收集的部分在post的评论中描述。

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所以没有任何引用来保持(所以没有什么可能被观察或稍后被垃圾回收)。 在这种情况下,立即在当前同步上下文中引发exception。 对于WPF应用程序, Dispatcher.UnhandledException将首先被触发,然后是Application.Current.DispatcherUnhandledException ,然后是AppDomain.CurrentDomain.UnhandledException 。 最后,如果没有处理这些事件( EventArgs.Handled未设置为true ),则无论ThrowUnobservedTaskExceptions设置如何,应用程序都将崩溃。 在这种情况下, TaskScheduler.UnobservedTaskException没有被激发,出于同样的原因:没有Task

编辑按@ Noseration的评论

async代码的.NET 4.5中,您可以通过注册TaskScheduler.UnobservedTaskException事件的处理程序来处理未被观察到的exception。 如果您不访问Task.ResultTask.Exception属性并且不调用Task.Wait ,则认为exception是不Task.Wait

在未查看的exception到达TaskScheduler.UnobservedTaskException事件处理程序后,默认行为是吞下此exception,以免程序崩溃。 通过添加以下内容,可以在configuration文件中更改此行为:

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

将事件绑定到AppDomain.CurrentDomain.FirstChanceException将保证您的exception将被捕获。 正如@Noseratio所指出的那样,即使在catch块中正常处理exception,并且应用程序继续执行,您的应用程序中的每个exception都会通知您。

不过,我仍然看到这个事件对于捕获在应用程序暂停之前抛出的最后一些exception或者其他一些debugging场景是有用的。

如果你想保护自己免受这个

 string x = await DoSomethingAsync(); 

我的build议是,不要这样做,添加一个try catch块:-)

那么,在这种情况下,你将如何定义一个应用程序全局处理程序来处理exception呢?

 string x = DoSomething(); 

机会是你的问题的答案是完全一样的。 看来你正在等待一个asynchronous方法,编译器会花费很多时间来确保asynchronous方法中发生的任何exception都以一种可以像在同步代码中那样处理它的方式传播和解除。 这是asynchronous/等待的主要好处之一。

您始终可以使用Application.DispatcherUnhandledException方法执行以下操作来处理exception。 当然,它会在TargetInvocationException给你,可能不如其他方法漂亮。 但它工作得很好

 _executeTask = executeMethod(parameter); _executeTask.ContinueWith(x => { Dispatcher.CurrentDispatcher.Invoke(new Action<Task>((task) => { if (task.Exception != null) throw task.Exception.Flatten().InnerException; }), x); }, TaskContinuationOptions.OnlyOnFaulted);