Страницы

Поиск по вопросам

четверг, 1 ноября 2018 г.

Необработанное исключение

На собеседовании спросили что будет в данной ситуации если оба Task выбросят исключение и как в данном случае их нужно обрабатывать?
Task task1 = Method1Async(); Task task2 = Method2Async(); await task1; await task2;


Ответ

Сперва немного теории. Как известно, async/await представляет по сути лишь синтаксический сахар, за кулисами же компилятор разворачивает асинхронный код в чуть более сложный. Что происходит, когда в методе, помеченном модификатором async, возникает исключение? Оно не выбрасывается тотчас наверх по стеку (потому что к тому моменту мы уже можем быть в совершенно другом месте), а помещается внутрь таска (см. свойство Exception). Сам метод при этом завершается нормально. Исключение же (оригинальное или обернутое в AggregateException) будет выброшено в нескольких случаях:
Кто-то ожидает таск (с помощью await или метода Wait()). Кто-то обращается к результату (свойство Result, применимо только для Task). В случае если таск остался unobserved (т.е. его никто не дождался и не обратился к результату), то когда сборщик мусора добирается до таска и вызывает его финализатор, то финализатор видит, что в таске есть исключение и выбрасывает его. Как следствие, процесс падает. Важное замечание: такое поведение было включено по умолчанию до .NET 4.5. Начиная с .NET 4.5, финализатор не выбрасывает исключение, однако прежнее поведение может быть возвращено с помощью настройки ThrowUnobservedTaskExceptions

Исходя из этого, ответ на вопрос несколько "ветвист":
Вариант 1. Код работает под .NET младше 4.5, либо включена настройка ThrowUnobservedTaskExceptions
В таком случае исключение для первого таска будет выброшено, исключение второго таска останется unobserved. Если где-то выше по стэку исключение от первого таска было обработано, и приложение продолжило работу, то рано оно поздно оно упадет -- когда финализатор второго таска выбросит исключение.
Вариант 2. Код работает под .NET 4.5 и выше и настройка ThrowUnobservedTaskExceptions выключена.
В таком случае исключение для первого таска по-прежнему будет выброшено, исключение второго таска останется unobserved (увидеть его можно будет только подписавшись на событие TaskScheduler.UnobservedTaskException), но процесс не упадет.
Вариантов обработки исключения несколько:
Обернуть каждый await в отдельный блок try/catch. Плюс этого варианта в том, что вы получите и обработаете оба исключения. Использовать await Task.WhenAll(task1, task2). Минус этого варианта в том, что вы получите информацию только о первом исключении. Использовать Task.WaitAll(task1, task2). Плюс этого варианта в том, что вы получите AggregateException, который будет содержать в себе оба исключения. Минус этого варианта в том, что он блокирует текущий поток.

Комментариев нет:

Отправить комментарий