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_UnobservedTaskException
和CurrentDomain_UnhandledException
都不被调用。 在等待或等待_task
对象之前,exception保持hibernate状态。 在上面的例子中,它永远不会被观察到, 所以TaskScheduler_UnobservedTaskException
只有在任务被垃圾收集时才会被调用 。 那么这个例外将被吞噬 。
旧的.NET 4.0行为, AppDomain.CurrentDomain.UnhandledException
事件被触发,应用程序崩溃,可以通过在app.config
configurationThrowUnobservedTaskExceptions
来启用:
<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.Result
, Task.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);