Ожидайте завершенную задачу, такую же, как task.Result?
В настоящее время я читаю "Concurrency в кулинарной книге С#" Стивена Клири, и я заметил следующую технику:
var completedTask = await Task.WhenAny(downloadTask, timeoutTask);
if (completedTask == timeoutTask)
return null;
return await downloadTask;
downloadTask - это вызов httpclient.GetStringAsync, а timeoutTask выполняет Task.Delay.
В случае отсутствия таймаута, downloadTask уже завершен. Зачем нужно делать второе ожидание, а не возвращать downloadTask.Result, учитывая, что задача уже завершена?
Ответы
Ответ 1
Здесь уже есть некоторые хорошие ответы/комментарии, но просто для звонка в...
Есть две причины, по которым я предпочитаю await
более Result
(или Wait
). Во-первых, обработка ошибок отличается; await
не завершает исключение в AggregateException
. В идеале, асинхронный код никогда не должен иметь дело с AggregateException
вообще, если он специально не хочет.
Вторая причина немного более тонкая. Как я описываю в своем блоге (и в книге), Result
/Wait
может вызвать взаимоблокировки, а может вызывают еще более тонкие взаимоблокировки при использовании в методе async
. Итак, когда я читаю код, и я вижу Result
или Wait
, что является предупреждающим флагом. Result
/Wait
является правильным, если вы абсолютно уверены, что задача уже завершена. Мало того, что это трудно увидеть с первого взгляда (в коде реального мира), но он также более хрупкий для изменения кода.
Чтобы не сказать, что Result
/Wait
никогда не следует использовать. Я следую этим правилам в своем собственном коде:
- Асинхронный код в приложении может использовать только
await
.
- Асинхронный код утилиты (в библиотеке) может иногда использовать
Result
/Wait
, если код действительно вызывает его. Такое использование должно иметь комментарии.
- Параллельный код задачи может использовать
Result
и Wait
.
Заметим, что (1), безусловно, является общим случаем, поэтому моя тенденция использовать await
всюду и рассматривать другие случаи как исключения для общего правила.
Ответ 2
Это имеет смысл, если timeoutTask
является произведением Task.Delay
, которое я считаю тем, что есть в книге.
Task.WhenAny
возвращает Task<Task>
, где внутренняя задача является одной из тех, которые вы передали в качестве аргументов. Его можно было бы переписать следующим образом:
Task<Task> anyTask = Task.WhenAny(downloadTask, timeoutTask);
await anyTask;
if (anyTask.Result == timeoutTask)
return null;
return downloadTask.Result;
В любом случае, поскольку downloadTask
уже завершен, существует очень незначительная разница между return await downloadTask
и return downloadTask.Result
. Он в том, что последний будет бросать AggregateException
, который обертывает любое исходное исключение, как указано в комментариях @KirillShlenskiy. Первый будет просто перебрасывать исходное исключение.
В любом случае, где бы вы не обрабатывали исключения, вы должны в любом случае проверить наличие AggregateException
и его внутренних исключений, чтобы получить причину ошибки.