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

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

string x = await DoSomethingAsync(); 

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

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

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

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

      

Когда этот способ включен, AppDomain.CurrentDomain.UnhandledException все равно будет запущен после TaskScheduler.UnobservedTaskException когда исключение получает garbage collection, а не на месте, где оно было выбрано.

Такое поведение описано Стивеном Тубом в блоге «Обработка исключений задач в .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 .

EDITED согласно комментарию @ Noseration

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

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

      

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

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

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

 string x = await DoSomethingAsync(); 

Мой совет вам, не делайте этого, добавляйте блок catch try 🙂

Вы всегда можете сделать следующее для обработки исключения, используя метод Application.DispatcherUnhandledException . Конечно, это будет дано вам внутри TargetInvocationException и может быть не таким красивым, как другие методы. Но он отлично работает

 _executeTask = executeMethod(parameter); _executeTask.ContinueWith(x => { Dispatcher.CurrentDispatcher.Invoke(new Action((task) => { if (task.Exception != null) throw task.Exception.Flatten().InnerException; }), x); }, TaskContinuationOptions.OnlyOnFaulted); 

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

 string x = DoSomething(); 

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

  • Ожидание против Задачи. Подождите - Тупик?
  • Async / ждет от BackgroundWorker
  • Parallel.ForEach и async-wait
  • Текущий SynchronizationContext не может использоваться как TaskScheduler
  • Где я могу найти версию streamа данных TPL для 4.0?
  • AspNetSynchronizationContext и ожидает продолжения в ASP.NET
  • Лучшая практика для вызова ConfigureAwait для всего кода на стороне сервера
  • Как обрабатывать все необработанные исключения при использовании параллельной библиотеки задач?
  • Создать завершенную задачу
  • Как я могу сообщить Moq о возврате задачи?
  • Как выполнить задачу на фоне wpf, когда вы можете предоставить отчет и разрешить аннулирование?
  • Давайте будем гением компьютера.