挖掘全球异常处理程序挖掘全球异常处理程序(TAP global exception handler)

2019-05-09 02:25发布

此代码会抛出异常。 是否有可能定义一个应用程序的全球处理器将抓住它?

string x = await DoSomethingAsync();

使用.NET 4.5 / WPF

Answer 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_UnobservedTaskException也不CurrentDomain_UnhandledException被调用。 直到例外保持休眠_task对象被等候已久的或。 在上面的例子中它永远不会被发现, 所以TaskScheduler_UnobservedTaskException只有当这项工作变得垃圾回收将被调用 。 然后这个异常会被吞噬

老.NET 4.0的行为,其中AppDomain.CurrentDomain.UnhandledException事件被炒鱿鱼和应用程序崩溃,可以通过配置来启用ThrowUnobservedTaskExceptionsapp.config

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

如果启用此方式, AppDomain.CurrentDomain.UnhandledException仍将被解雇 TaskScheduler.UnobservedTaskException当异常变垃圾回收,而不是它抛出现货。

此行为是由斯蒂芬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");
}

因为它是async void没有Task参考扶住(所以没有什么可以观察到可能或垃圾收集后)。 在这种情况下,该异常被当前同步上下文立即抛出。 对于一个WPF应用程序, Dispatcher.UnhandledException会先启用,然后Application.Current.DispatcherUnhandledException ,然后AppDomain.CurrentDomain.UnhandledException 。 最后,如果没有这些事件的处理( EventArgs.Handled没有被设置为true ),应用程序会崩溃,而不管的ThrowUnobservedTaskExceptions设置。 TaskScheduler.UnobservedTaskException 没有在这种情况下被解雇,出于同样的原因:没有Task



Answer 2:

市价修改 @ Noseration的评论

在.NET 4.5 async代码,你可以通过注册一个处理程序处理没有观察到异常TaskScheduler.UnobservedTaskException事件。 一个例外是不可观测认为,如果你不访问Task.ResultTask.Exception性质和你不叫Task.Wait

未观察到的异常到达后TaskScheduler.UnobservedTaskException事件处理程序,默认行为是吞下这个例外,这样的程序不会崩溃。 这种行为可以在配置文件中加入下面的改变:

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


Answer 3:

绑定事件到AppDomain.CurrentDomain.FirstChanceException会向你保证,你的异常将被捕获。 作为@Noseratio指出的那样,你会通知应用程序中每个异常,即使异常的catch块内的正常处理和应用程序继续进行。

不过,我仍然看到至少捕捉抛出的最后几个例外前申请停止或可能还有一些其他的调试情况下该事件是有用的。

如果你想保护自己免受这

string x = await DoSomethingAsync();

我给你的建议是,不这样做,将一个try catch块:-)



Answer 4:

你总是可以做以下的使用来处理异常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);


Answer 5:

那么,你如何定义一个应用程序的全局处理程序来处理这种情况的一个例外?

string x = DoSomething();

机会是在回答你的问题是完全一样的。 您似乎在等待正确的异步方法,编译器去竭尽全力,以确保发生在异步方法的任何异常的方式,让你来处理它,就像你在同步代码传播和展开。 这是异步的主要优点之一/等待。



文章来源: TAP global exception handler