Глобальный обработчик исключений TAP

Этот код генерирует исключение. Можно ли определить глобальный обработчик приложения, который его поймает?

string x = await DoSomethingAsync();

Использование .net 4.5/WPF

Ответ 1

На самом деле это хороший вопрос, если я правильно понял. Сначала я проголосовал за его закрытие, но теперь отозвал свой голос.

Важно понимать, как исключение, генерируемое внутри async Task метода 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 объект _task будет _task или _task. В приведенном выше примере это никогда не наблюдается, поэтому TaskScheduler_UnobservedTaskException будет вызываться только тогда, когда задача будет TaskScheduler_UnobservedTaskException от мусора. Тогда это исключение будет проглочено.

Старое поведение .NET 4.0, когда происходит событие AppDomain.CurrentDomain.UnhandledException и происходит сбой приложения, может быть включено путем настройки ThrowUnobservedTaskExceptions в app.config:

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

Если этот параметр включен, AppDomain.CurrentDomain.UnhandledException будет по-прежнему вызываться после TaskScheduler.UnobservedTaskException когда исключение будет собираться мусором, а не на месте, где оно было выброшено.

Это поведение описано Стивеном Таубом в его блоге "Обработка исключений задач в .NET 4.5". Часть о сборке мусора задачи описана в комментариях к посту.

Это в случае с async Task методами 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.

Ответ 2

EDITED в соответствии с комментарием @Noseration

В .NET 4.5 в коде async вы можете обрабатывать незаметные исключения, зарегистрировав обработчик для события TaskScheduler.UnobservedTaskException. Исключение считается ненаблюдаемым, если вы не получаете доступ к свойствам Task.Result, Task.Exception, и вы не вызываете Task.Wait.

После того, как незаметное исключение достигнет обработчика событий TaskScheduler.UnobservedTaskException, поведение по умолчанию заключается в том, чтобы проглатывать это исключение, поэтому программа не сбой. Это поведение можно изменить в файле конфигурации, добавив следующее:

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

Ответ 3

Привязка события к AppDomain.CurrentDomain.FirstChanceException гарантирует, что ваше исключение будет обнаружено. Как отметил @Noseratio, вы будете уведомлены обо всех исключениях в своем приложении, даже если исключение обрабатывается изящно в блоке catch, и приложение продолжается.

Тем не менее, я по-прежнему вижу, что это событие полезно, по крайней мере, для захвата последних нескольких исключений, возникших до того, как приложение остановилось или, возможно, какой-то другой сценарий отладки.

Если вы хотите защитить себя от этого

string x = await DoSomethingAsync();

Мой совет вам, не делайте этого, добавьте блок catch try: -)

Ответ 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);

Ответ 5

Хорошо, как бы вы определили глобальный обработчик приложения для устранения исключения в этом случае?

string x = DoSomething();

Скорее всего, ответ на ваш вопрос точно такой же. Похоже, вы ожидаете асинхронного метода, и компилятор идет на большие длины, чтобы гарантировать, что любое исключение, возникающее в методе async, распространяется и разматывается таким образом, чтобы вы могли обрабатывать его так же, как и в синхронном коде. Это одно из основных преимуществ async/wait.