【问题标题】:TAP global exception handlerTAP 全局异常处理程序
【发布时间】:2014-04-17 15:07:01
【问题描述】:

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

string x = await DoSomethingAsync();

使用 .net 4.5 / WPF

【问题讨论】:

  • 围绕该语句的 try/catch 有什么问题?
  • 提示创建 Applicaton.DispatcherUnhandledException 的相同内容。
  • 那是为可能发生的您无法捕获的异常而创建的。通常,您应该捕获代码中可能出现的异常。但是,这里有一些链接可能会帮助您相处:stackoverflow.com/questions/14167746/… & msdn.microsoft.com/en-us/library/…
  • 这段代码怎么叫?你已经尝试过什么?

标签: c# .net exception task-parallel-library async-await


【解决方案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 都不会被调用。该异常一直处于休眠状态,直到 _task 对象被等待或等待。在上面的例子中,它永远不会被观察到,所以TaskScheduler_UnobservedTaskException只会在任务被垃圾回收时被调用。那么这个异常就会被吞下

可以通过在app.config 中配置ThrowUnobservedTaskExceptions 来启用旧的.NET 4.0 行为,其中AppDomain.CurrentDomain.UnhandledException 事件被触发并且应用程序崩溃:

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

当以这种方式启用时,AppDomain.CurrentDomain.UnhandledException 仍将在TaskScheduler.UnobservedTaskException 异常被垃圾收集时触发,而不是在它抛出的地方。

Stephen Toub 在他的"Task Exception Handling in .NET 4.5" 博客文章中描述了这种行为。关于任务垃圾收集的部分在帖子的 cmets 中有描述。

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

【讨论】:

  • Noseratio 你很清楚我的问题。然而,在运行 GC 之前不会引发 UnobservedTaskException 的事实使得它在功能上毫无用处。 Toub 在他的帖子的问题部分提供了 IgnoreExceptions 解决方案。我不明白为什么编译器在创建异步方法包装器时不添加它。如果事件被处理,它应该在延续内部引发 UnobservedTaskExpception。
  • @Sam,IMO 这种行为非常合理。 Task 对象是一个承诺,一个延迟的结果。它刚刚完成的事实并不意味着必须立即观察它。由于与其他任务(例如WhenAll)或异步逻辑的其他细节组合,可能会在 10 分钟后观察到它。为什么编译器要对此做出任何假设并打破这种逻辑?它不应该。
  • 您可以完全控制它。要么使用try/catch 在可能发生的地方处理异常,要么使用上述IgnoreException 之类的东西将其标记为已观察。
  • 谢谢 Noseratio,我的意思是我需要一种方法来创建“最后的观察者”或全局处理程序。如果我处理 DispatcherUnhandledException,我并不是在要求编译器做出假设——我是在给它一个特定的指令。如果编译器添加 Toubs IgnoreExceptions 或类似的东西,如果我处理它,它可以触发 DispatcherUnhandledException。
  • 顺便说一句,看看 Todd Menier 提供的答案。虽然他的回答是错误的,但他的推理是正确的。即 - 我可以为非异步应用程序定义一个全局处理程序,为什么我不能为异步应用程序定义一个?
【解决方案2】:

您始终可以使用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);

【讨论】:

    【解决方案3】:

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

    但是,我仍然认为此事件至少对于捕获在应用程序停止之前抛出的最后几个异常或可能是其他一些调试场景很有用。

    如果您想保护自己免受这种情况的影响

    string x = await DoSomethingAsync();
    

    我给你的建议是,不要那样做,添加一个 try catch 块:-)

    【讨论】:

      【解决方案4】:

      已编辑根据@Noseration 的评论

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

      未观察到的异常到达TaskScheduler.UnobservedTaskException 事件处理程序后,默认行为是吞下此异常,因此程序不会崩溃。可以通过添加以下内容在配置文件中更改此行为:

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

      【讨论】:

      • 是的,但在这种情况下,他正在等待结果,因此该任务将被视为已观察到。
      • 这对于 .NET 4.0 ,但对于 .NET 4.5+ 不再适用。详情请查看my answer
      【解决方案5】:

      那么,在这种情况下,您将如何定义应用程序全局处理程序来处理异常?

      string x = DoSomething();
      

      很有可能您的问题的答案完全相同。看来您正在正确地等待异步方法,并且编译器竭尽全力确保异步方法中发生的任何异常都以允许您像在同步代码中一样处理它的方式传播和展开。这是 async/await 的主要好处之一。

      【讨论】:

        猜你喜欢
        • 2012-12-31
        • 2011-05-19
        • 2011-08-31
        • 2010-12-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-01-16
        • 2011-04-07
        相关资源
        最近更新 更多