Ждите выполнения задачи так же, как и задачи.Результат?


в настоящее время я читаю "параллелизм в C# Cookbook" Стивена Клири, и я заметил следующую технику:

var completedTask = await Task.WhenAny(downloadTask, timeoutTask);  
if (completedTask == timeoutTask)  
  return null;  
return await downloadTask;  

downloadTask - это вызов httpclient.GetStringAsync, и timeoutTask задача выполняется.Задержка.

в случае, если это не тайм-аут, то downloadTask уже завершена. Почему необходимо сделать второе ожидание вместо возврата задачи загрузки.Результат, учитывая, что задача уже закончили?

2 79

2 ответа:

уже есть несколько хороших ответов/комментариев, но просто, чтобы поучаствовать в обсуждении...

есть две причины, почему я предпочитаю await over Result (или Wait). Во-первых, что обработка ошибок отличается; await не переносит исключение в AggregateException. В идеале, асинхронный код никогда не должен иметь дело с AggregateException вообще, если это специально хочет для.

вторая причина-немного более тонкая. Как я описываю в своем блоге (и в книге), Result/Wait может привести к взаимоблокировкам и может вызвать еще более тонкие тупики при использовании в async метод. Итак, когда я читаю код и вижу Result или Wait, это немедленное предупреждение. Элемент Result/Wait правильно только если вы абсолютно точно что задача уже завершена. Это не только трудно увидеть с первого взгляда (в реальном коде), но и более хрупко для кода изменения.

это не значит, что Result/Wait должны никогда использоваться. Я следую этим рекомендациям в своем собственном коде:

  1. асинхронный код в приложении можно использовать только await.
  2. асинхронный код утилиты (в библиотеке) можно иногда использовать Result/Wait если код действительно требует этого. Такое использование, вероятно, должно иметь комментарии.
  3. параллельно код задачи может использовать Result и Wait.

обратите внимание, что (1) на сегодняшний день является общим случаем, поэтому моя тенденция использовать await везде и рассматривать другие случаи как исключения из общего правила.

это имеет смысл, если 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 который обертывает любое исходное исключение, как указано @Кириллшленский в комментариях. Первый просто повторно бросит исходное исключение.

в любом случае, где бы вы ни обрабатывали исключения, вы должны проверить AggregateException и его внутренних исключений в любом случае, чтобы добраться до причины ошибки.