День двести семнадцатый. #ЗаметкиНаПолях
Многопоточность.
9. Async/await. Продолжение
Обработка исключений
Когда вы ожидаете завершения асинхронной операции, то возможно, она давным-давно завершилась неудачей в совершенно другом потоке. В этом случае обычный синхронный способ распространения исключений в стеке не подходит. Вместо этого инфраструктура async/await предпринимает некоторые шаги, чтобы опыт обработки асинхронных сбоев был максимально похож на синхронные сбои.
Метод
- Свойство
- Свойство
- Метод
- Свойство
Если задача отменяется через
При вызове
Предположим, вы хотите выполнить некоторую работу в асинхронном методе после проверки параметра на ненулевое значение:
Во многих ситуациях такая жадная проверка аргументов может быть полезна, да и разница во времени может быть не критична, однако, это в любом случае нужно иметь в виду.
Продолжение следует…
Источник: Jon Skeet “C# In Depth”. 4th ed – Manning Publications Co, 2019. Глава 5.
Многопоточность.
9. Async/await. Продолжение
Обработка исключений
Когда вы ожидаете завершения асинхронной операции, то возможно, она давным-давно завершилась неудачей в совершенно другом потоке. В этом случае обычный синхронный способ распространения исключений в стеке не подходит. Вместо этого инфраструктура async/await предпринимает некоторые шаги, чтобы опыт обработки асинхронных сбоев был максимально похож на синхронные сбои.
Метод
GetResult()
ожидателя предназначен как для извлечения возвращаемого значения, если оно есть, так и для распространения любых исключений, возникших в асинхронной операции. Это не так просто, как кажется, потому что в асинхронном мире одна задача может состоять из несколько операций, которые приведут к нескольким сбоям. Task
и Task<TResult>
указывают на сбои несколькими способами:- Свойство
Status
становится Faulted
, если асинхронная операция не выполнена (а IsFaulted
возвращает значение true
).- Свойство
Exception
возвращает AggregateException
, которое содержит все (возможно несколько) исключений, которые привели к сбою задачи, либо null
если задача завершилась успешно.- Метод
Wait()
выбрасывает исключение AggregateException
, если задача заканчивается неудачей.- Свойство
Result
для Task<TResult>
(которое также приводит к ожиданию завершения задачи) аналогично выбрасывает AggregateException
.Если задача отменяется через
CancellationToken
, метод Wait()
и свойство Result
генерируют исключение AggregateException
, содержащее исключение OperationCanceledException
, но при этом свойство Status
устанавливается в Canceled
(см. пост про отмену задания https://t.me/NetDeveloperDiary/219).При вызове
await
, если задача завершается неудачей или отменяется, выбрасывается исключение, но не AggregateException
. Вместо этого для удобства выбрасывается первое исключение из коллекции AggregateException
. В большинстве случаев это то, что вам нужно. Однако это может привести к потере информации. Если в задаче возникает несколько исключений, GetResult
выдаёт только первое из них. Возможно, вы захотите переписать код, чтобы при сбое вызывающая сторона могла перехватить AggregateException
и изучить все причины сбоя. Некоторые методы фреймворка делают это. Например, Task.WhenAll()
, который асинхронно ожидает завершения всех задач, указанных в аргументе метода. Например:var tasks = new Task<string>[]{ … };Если какие-либо из задач дают сбой, результатом вызова
…
Task<string[]> results = Task.WhenAll(tasks);
foreach (var result in results.Result) {…}
results.Result
является AggregateException
, который будет содержать исключения из всех задач, завершившихся неудачей. Но если вы вызовете await
для Task.WhenAll()
, вы увидите только первое исключение:string[] results = await Task.WhenAll(tasks);Наиболее важный момент, который следует отметить в отношении исключений, заключается в том, что асинхронный метод никогда не генерирует исключение напрямую. Даже если первое, что делает тело метода, это генерирует исключение, метод вернет задачу в статусе
Faulted
.Предположим, вы хотите выполнить некоторую работу в асинхронном методе после проверки параметра на ненулевое значение:
Task<int> task = DoSomeWork(null);Если вы проверяете параметры внутри асинхронного метода
…
int result = await task;
DoSomeWork
, вызывающая сторона не получит никакого уведомления об ошибке до вызова await
задачи.Во многих ситуациях такая жадная проверка аргументов может быть полезна, да и разница во времени может быть не критична, однако, это в любом случае нужно иметь в виду.
Продолжение следует…
Источник: Jon Skeet “C# In Depth”. 4th ed – Manning Publications Co, 2019. Глава 5.