Этот код генерирует исключение. Можно ли определить глобальный обработчик приложения, который его поймает?
string x = await DoSomethingAsync();
Использование .net 4.5/WPF
Этот код генерирует исключение. Можно ли определить глобальный обработчик приложения, который его поймает?
string x = await DoSomethingAsync();
Использование .net 4.5/WPF
На самом деле это хороший вопрос, если я правильно понял. Сначала я проголосовал за его закрытие, но теперь отозвал свой голос.
Важно понимать, как исключение, генерируемое внутри 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
.
EDITED в соответствии с комментарием @Noseration
В .NET 4.5 в коде async
вы можете обрабатывать незаметные исключения, зарегистрировав обработчик для события TaskScheduler.UnobservedTaskException
. Исключение считается ненаблюдаемым, если вы не получаете доступ к свойствам Task.Result
, Task.Exception
, и вы не вызываете Task.Wait
.
После того, как незаметное исключение достигнет обработчика событий TaskScheduler.UnobservedTaskException
, поведение по умолчанию заключается в том, чтобы проглатывать это исключение, поэтому программа не сбой. Это поведение можно изменить в файле конфигурации, добавив следующее:
<configuration>
<runtime>
<ThrowUnobservedTaskExceptions enabled="true"/>
</runtime>
</configuration>
Привязка события к 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>((task) =>
{
if (task.Exception != null)
throw task.Exception.Flatten().InnerException;
}), x);
}, TaskContinuationOptions.OnlyOnFaulted);
Хорошо, как бы вы определили глобальный обработчик приложения для устранения исключения в этом случае?
string x = DoSomething();
Скорее всего, ответ на ваш вопрос точно такой же. Похоже, вы ожидаете асинхронного метода, и компилятор идет на большие длины, чтобы гарантировать, что любое исключение, возникающее в методе async, распространяется и разматывается таким образом, чтобы вы могли обрабатывать его так же, как и в синхронном коде. Это одно из основных преимуществ async/wait.