此代码抛出异常。是否可以定义一个能够捕获它的应用程序全局处理程序?
string x = await DoSomethingAsync();
使用.net 4.5 / WPF
答案 0 :(得分:20)
如果我理解正确的话,这实际上是一个好的问题。我最初投票决定关闭它,但现在撤回了我的投票。
了解在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_UnobservedTaskException
和CurrentDomain_UnhandledException
都不会被调用。在等待或等待_task
对象之前,异常仍然处于休眠状态。在上面的示例中,它永远不会被观察到,因此只有在任务被垃圾收集时才会调用TaskScheduler_UnobservedTaskException
。然后,此异常将被吞噬。
可以通过在AppDomain.CurrentDomain.UnhandledException
中配置ThrowUnobservedTaskExceptions
来启用导致app.config
事件被触发且应用崩溃的旧.NET 4.0行为:
<configuration>
<runtime>
<ThrowUnobservedTaskExceptions enabled="true"/>
</runtime>
</configuration>
以这种方式启用时,{<1}}仍会在 AppDomain.CurrentDomain.UnhandledException
之后被触发,当异常被垃圾收集时,而不是在它被抛出的位置。
Stephen Toub在他的"Task Exception Handling in .NET 4.5"博客文章中描述了这种行为。关于任务垃圾收集的部分在帖子的评论中描述。
TaskScheduler.UnobservedTaskException
方法就是这种情况。 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
引用来保持(所以没有什么可能被观察到或以后垃圾收集)。在这种情况下,会立即在当前同步上下文中抛出异常。对于WPF应用,Task
将首先触发,然后Dispatcher.UnhandledException
,然后Application.Current.DispatcherUnhandledException
。最后,如果没有处理这些事件(AppDomain.CurrentDomain.UnhandledException
未设置为EventArgs.Handled
),则无论true
设置如何,应用都会崩溃。在这种情况下ThrowUnobservedTaskExceptions
不被解雇,原因相同:没有TaskScheduler.UnobservedTaskException
。
答案 1 :(得分:1)
已编辑
在{4.5} async
代码中,您可以通过注册TaskScheduler.UnobservedTaskException
事件的处理程序来处理未观察到的异常。如果您未访问Task.Result
,Task.Exception
媒体资源且未致电Task.Wait
,则视为未观察到例外情况。
在未观察到的异常到达TaskScheduler.UnobservedTaskException
事件处理程序之后,默认行为是吞下此异常,以便程序不会崩溃。可以通过添加以下内容在配置文件中更改此行为:
<configuration>
<runtime>
<ThrowUnobservedTaskExceptions enabled="true"/>
</runtime>
</configuration>
答案 2 :(得分:1)
将事件绑定到AppDomain.CurrentDomain.FirstChanceException
将保证您的异常将被捕获。正如@Noseratio指出的那样,即使在catch块中正常处理异常并且应用程序继续运行,您也会收到应用程序中每个异常的通知。
但是,我仍然认为此事件至少可以捕获应用程序暂停之前引发的最后几个异常,或者可能是某些其他调试方案。
如果你想保护自己免受这个
string x = await DoSomethingAsync();
我的建议是,不要这样做,添加一个try catch块: - )
答案 3 :(得分:1)
您始终可以使用Application.DispatcherUnhandledException
方法执行以下操作来处理异常。当然它会在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);
答案 4 :(得分:0)
那么,在这种情况下,如何定义应用程序全局处理程序来处理异常?
string x = DoSomething();
机会是你的问题的答案是完全一样的。您似乎正在等待异步方法,并且编译器竭尽全力确保异步方法中发生的任何异常都以允许您处理它的方式传播和展开,就像在同步代码中一样。这是async / await的主要优点之一。