Ожидать выполненной задачи так же, как и task.Result?

117

В настоящее время я читаю « Поваренную книгу о параллелизме в C # » Стивена Клири и заметил следующий прием:

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

downloadTaskявляется вызовом httpclient.GetStringAsyncи timeoutTaskвыполняется Task.Delay.

В том случае, если он не истек, значит, downloadTaskон уже завершен. Почему необходимо выполнить второе ожидание вместо возврата downloadTask.Result, если задача уже выполнена?

julio.g
источник
3
Здесь не хватает контекста, и, если у людей нет доступа к книге, вам нужно будет включить ее. Что такое downloadTaskи timeoutTask? Что они делают?
Mike Perrenoud 08
7
Я не вижу здесь фактической проверки успешного завершения. Задача вполне может быть ошибочной, и в этом случае поведение будет другим ( AggregateExceptionс Resultпервым исключением vs через ExceptionDispatchInfowith await). Более подробно обсуждается в статье Стивена Туба «Обработка исключений в задачах в .NET 4.5»: blogs.msdn.com/b/pfxteam/archive/2011/09/28/… )
Кирилл Шленский 08
вы должны дать этот ответ @KirillShlenskiy
Carsten
@MichaelPerrenoud Вы правы, спасибо, что заметили, я отредактирую вопрос.
julio.g

Ответы:

160

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

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

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

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

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

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

Стивен Клири
источник
Мы столкнулись с тупиком, используя в наших проектах «результат» вместо «ожидание». испорченная часть не имеет ошибки компиляции, и ваш код через некоторое время становится нестабильным.
Ахмад Мусави
@Stephen, не могли бы вы объяснить мне, почему «В идеале асинхронный код вообще не должен иметь дело с AggregateException, если только он специально не хочет»
vcRobe
4
@vcRobe Потому что awaitмешает AggregateExceptionобертка. AggregateExceptionбыл разработан для параллельного программирования, а не для асинхронного программирования.
Стивен Клири
2
> «Ждать правильно, только если вы абсолютно уверены, что задача уже выполнена». .... Тогда почему это называется Подождите?
Райан Лич
4
@RyanTheLeach: Первоначальной целью Waitбыло присоединение к экземплярам динамического параллелизма задач Task . Использование его для ожидания асинхронных Taskэкземпляров опасно. Microsoft рассматривала возможность введения нового типа «Promise», но предпочла использовать существующий Task; компромисс повторного использования существующего Taskтипа для асинхронных задач заключается в том, что в итоге вы получаете несколько API, которые просто не должны использоваться в асинхронном коде.
Стивен Клири
12

Это имеет смысл, если 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все равно должны проверять наличие и его внутренние исключения, чтобы выяснить причину ошибки.

noseratio
источник