Глобальный обработчик исключений TAP
Этот код генерирует исключение. Можно ли определить глобальный обработчик приложения, который его поймает?
string x = await DoSomethingAsync();
Использование .net 4.5 / WPF
- Вложенность в Parallel.ForEach
- Как отменить задачу в ожидании?
- Асинхронно ждать Task , чтобы завершить таймаут
- Почему TaskScheduler.Current по умолчанию TaskScheduler?
- Поймать исключение, вызванное асинхронным методом
- SynchronizationContext.Current имеет значение null в продолжении в основном streamе пользовательского интерфейса
- Самый простой способ сделать огонь и забыть метод в c # 4.0
- В чем разница между Task.Start / Wait и Async / Await?
- Параметры TaskCreationOptions.LongRunning и ThreadPool
- Пример async / wait, который вызывает тупик
- Is Task.Run считается плохой практикой в веб-приложении ASP .NET MVC?
- В чем разница между задачей и streamом?
- Перекачка сообщений StaTaskScheduler и STA
Это действительно хороший вопрос, если я правильно понял. Я изначально проголосовал за его закрытие, но теперь отказался от своего голоса.
Важно понять, как исключение, созданное внутри метода 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.