Страницы

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

пятница, 20 декабря 2019 г.

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

#c_sharp #net #исключения #async_await


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

Task task1 = Method1Async();
Task task2 = Method2Async();
await task1;
await task2;

    


Ответы

Ответ 1



Сперва немного теории. Как известно, 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, который будет содержать в себе оба исключения. Минус этого варианта в том, что он блокирует текущий поток.

Ответ 2



Вопрос взят отсюда. Короткий ответ — процесс упадет до 4.5, а после — нет.

Ответ 3



Небольшое дополнение к ответу @andreycha (первый вариант обработки): если вы хотите получить все исключения в AggregateException, но при этом не использовать блокирующий Task.WaitAll, можно использовать такой вот велосипед: static async Task WhenAllWithAllExceptions(IEnumerable tasks) { var materialTasks = tasks.ToList(); // пробегаем список сейчас List exceptions = null; bool hasExceptions = false; foreach (var task in materialTasks) { try { await task.ConfigureAwait(false); } catch (Exception ex) { if (exceptions == null) exceptions = new List(materialTasks.Count); exceptions.Add(ex); hasExceptions = true; } } if (hasExceptions) throw new AggregateException(exceptions); } static async Task WhenAllWithAllExceptions(IEnumerable> tasks) { var materialTasks = tasks.ToList(); // пробегаем список сейчас T[] results = null; List exceptions = null; bool hasExceptions = false; int taskIndex = -1; foreach (var task in materialTasks) { taskIndex++; try { T t = await task.ConfigureAwait(false); if (!hasExceptions) { if (results == null) results = new T[materialTasks.Count]; results[taskIndex] = t; } } catch (Exception ex) { if (exceptions == null) exceptions = new List(materialTasks.Count); exceptions.Add(ex); hasExceptions = true; } } if (hasExceptions) throw new AggregateException(exceptions); return results; } Обновление: по совету @Pavel Mayorov, добавил ConfigureAwait.

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

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