Перехват необработанных исключений из асинхронного режима

Когда ожидаемый метод async генерирует исключение, это исключение где-то сохраняется, и его создание задерживается. В приложении WinForms или WPF он использует SynchronizationContext.Current для сообщения об исключении. Однако, в т.ч. консольное приложение, оно генерирует исключение в пуле потоков и останавливает приложение.

Как предотвратить прекращение работы приложения из-за исключений, вызванных методом async?

ИЗМЕНИТЬ:

По-видимому, проблема, которую я описываю, связана с тем, что у меня есть void async методов. Смотрите комментарии.


person Pieter van Ginkel    schedule 07.09.2012    source источник
comment
Если ожидается асинхронный метод, исключение создается в коде, который его ожидает. Необработанные исключения ведут себя таким образом, только если метод void возвращает значение. Это причина по возможности избегать использования методов async void.   -  person svick    schedule 07.09.2012
comment
Методы @svick async void приводят к детерминированному поведению; вызов функции, возвращающей Task, которая приводит к исключению, и не ожидая выполнения задачи, вызовет событие UnobservedTaskException в какой-то полуслучайный момент в будущем, когда запустится сборщик мусора, и если это ничего не даст, программа продолжит работу, как будто все в порядке . Проблема не в методах async void, они просто раскрывают реальную проблему. Если вы не вызываете функцию, возвращающую Task, из метода async, велика вероятность, что вы делаете что-то не так.   -  person    schedule 07.09.2012
comment
Кстати, только хороший шанс, потому что есть несколько исключительных (без каламбура) случаев, когда можно отбрасывать исключения, но не во всех случаях.   -  person    schedule 07.09.2012
comment
@hvd: обработчики событий должны быть пустыми. Почему UnobservedTaskException не является проблемой?   -  person Pieter van Ginkel    schedule 07.09.2012
comment
@hvd: я думаю, что async Task охватывает естественно недетерминированный характер асинхронного программирования. С async void вы все равно получите исключение в какой-то полуслучайный момент в будущем, которое (обычно) передается непосредственно в ваш основной цикл. Я думаю, что код будет чище, если вы используете async Task и убедитесь, что они awaited. ИМО. :)   -  person Stephen Cleary    schedule 07.09.2012
comment
Методы @Pieter async void наблюдают за исключением, отправляя его в контекст синхронизации. async void f() { await g(); } в порядке. void f() { var task = g(); /* ignore task */ } нет.   -  person    schedule 07.09.2012
comment
@StephenCleary Это не точно определенная точка в будущем, но и не случайная, в отличие от сборки мусора. :) И в какой-то момент функция, не возвращающая задачу, запустит асинхронную операцию, и эта функция, не возвращающая задачу, обычно должна быть методом async void. Я думаю, вы имеете в виду, что один метод должен быть как можно выше, все остальные должны просто возвращать задачу. Если да, то конечно, тут не поспоришь.   -  person    schedule 07.09.2012
comment
@hvd: хорошо, не случайно. :) Однако я не согласен с необходимостью async void. У вас будет один, если вам нужен обработчик событий async (обычно в приложениях пользовательского интерфейса), но не иначе. ASP.NET WebAPI и MVC предоставляют вам большинство точек входа верхнего уровня как async Task. Для ситуации оператора (консольные приложения) я по-прежнему предпочитаю async Task async void.   -  person Stephen Cleary    schedule 07.09.2012
comment
@StephenCleary Ах, конечно, когда самый верхний асинхронный метод скрыт в используемой вами библиотеке, вы можете избежать использования async void в своем собственном коде. Не знал, что такое существует, прикольно, спасибо за информацию. FWIW, что я делаю в своем собственном коде, так это использую метод async void, который не делает ничего, кроме ожидания другой задачи, перехватывая определенные типы исключений, которые можно безопасно игнорировать (в основном, OperationCanceledException), и ничего не делая с ними. Все преимущества обоих вариантов.   -  person    schedule 07.09.2012


Ответы (2)


Как я могу предотвратить остановку приложения из-за исключений, вызванных асинхронным методом?

Следуйте этим рекомендациям:

  1. Все методы async должны возвращать Task или Task<T>, кроме случаев, когда они должны возвращать void (например, обработчики событий).
  2. В какой-то момент вы должны await все Task вернуть из async методов. Единственная причина, по которой вы не захотите этого делать, — это если вас больше не волнует результат операции (например, после ее отмены).
  3. Если вам нужно перехватить исключение из обработчика событий async void, перехватите его в обработчике событий — точно так же, как если бы это был синхронный код.

Вы можете найти мой async / await вступительный пост полезным; Я также рассказываю о нескольких других передовых методах.

person Stephen Cleary    schedule 07.09.2012
comment
Я понимаю, что проблема, с которой я сталкиваюсь, заключается в том, что у меня есть void async методов. Однако означает ли это, что проблема, которую я описываю, действительно возникает для обработчиков событий? - person Pieter van Ginkel; 07.09.2012
comment
Да; чтобы повторить третий пункт, если у вас есть обработчик событий async void, вы, вероятно, захотите перехватывать исключения внутри этого обработчика событий. Синхронный обработчик событий будет иметь точно такую ​​же проблему — он приведет к сбою приложения, если возникнет исключение во время его работы в потоке пула потоков. - person Stephen Cleary; 07.09.2012
comment
За исключением того, что исключения направляются в SynchronizationContext, который был захвачен при вызове метода async. В приложении WinForms/WPF большую часть времени это будет правильный контекст синхронизации, поскольку событие, скорее всего, будет запущено из потока пользовательского интерфейса. Похоже, поведение не сильно отличается от того, что происходит, когда событие не async. - person Pieter van Ginkel; 07.09.2012
comment
Маршрутизация SynchronizationContext выполняется для имитации поведения синхронного обработчика событий. Исключения как от синхронных обработчиков событий, так и от async void обработчиков событий попадают в контекст. Это верно независимо от того, является ли контекст пользовательским интерфейсом, ASP.NET или пулом потоков. - person Stephen Cleary; 07.09.2012
comment
Спасибо! Синхронизация потоков может решить проблему, но это правильный путь. - person Rushui Guan; 21.09.2013

Когда метод async запускается, он фиксирует текущий контекст синхронизации. Способ решить эту проблему — создать собственный контекст синхронизации, который фиксирует исключение.

Дело здесь в том, что контекст синхронизации отправляет обратный вызов в пул потоков, но с попыткой/поймать вокруг него:

public class AsyncSynchronizationContext : SynchronizationContext
{
    public override void Send(SendOrPostCallback d, object state)
    {
        try
        {
            d(state);
        }
        catch (Exception ex)
        {
            // Put your exception handling logic here.

            Console.WriteLine(ex.Message);
        }
    }

    public override void Post(SendOrPostCallback d, object state)
    {
        try
        {
            d(state);
        }
        catch (Exception ex)
        {
            // Put your exception handling logic here.

            Console.WriteLine(ex.Message);
        }
    }
}

В catch выше вы можете поместить свою логику обработки исключений.

Затем в каждом потоке (SynchronizationContext.Current есть [ThreadStatic]), где вы хотите выполнить async методов с помощью этого механизма, вы должны установить текущий контекст синхронизации:

SynchronizationContext.SetSynchronizationContext(new AsyncSynchronizationContext());

Полный пример Main:

class Program
{
    static void Main(string[] args)
    {
        SynchronizationContext.SetSynchronizationContext(new AsyncSynchronizationContext());

        ExecuteAsyncMethod();

        Console.ReadKey();
    }

    private static async void ExecuteAsyncMethod()
    {
        await AsyncMethod();
    }

    private static async Task AsyncMethod()
    {
        throw new Exception("Exception from async");
    }
}
person Pieter van Ginkel    schedule 07.09.2012
comment
Извините, но я не вижу, где именно ваш SynchronizationContext публикует что-либо в пуле потоков - насколько я вижу, все, что он делает, это немедленно выполняет функцию в вызывающем потоке и возвращает. - person Rafael Munitić; 24.05.2013