Aguarde em uma tarefa concluída o mesmo que task.Result?

Eu estou lendo atualmente “Concurrency in C # Cookbook” por Stephen Cleary, e notei a seguinte técnica:

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

downloadTask é uma chamada para httpclient.GetStringAsync e timeoutTask está executando Task.Delay.

No caso de não expirar, o downloadTask já estará concluído. Por que é necessário fazer um segundo aguardar em vez de retornar o downloadTask.Result, uma vez que a tarefa já está concluída?

Já existem algumas boas respostas / comentários aqui, mas apenas para …

Existem duas razões pelas quais eu prefiro await por cima do Result (ou Wait ). A primeira é que o tratamento de erros é diferente; await não quebra a exceção em um AggregateException . Idealmente, o código asynchronous nunca deve ter que lidar com o AggregateException , a menos que ele queira especificamente.

A segunda razão é um pouco mais sutil. Como descrevo no meu blog (e no livro), Result / Wait pode causar deadlocks e pode causar conflitos ainda mais sutis quando usado em um método async . Então, quando estou lendo o código e vejo um Result ou Wait , esse é um sinalizador de aviso imediato. O Result / Wait só está correto se você tiver certeza absoluta de que a tarefa já está concluída. Não só isso é difícil de ver de relance (no código do mundo real), mas também é mais frágil para codificar as alterações.

Isso não quer dizer que o Result / Wait nunca deve ser usado. Eu sigo estas orientações no meu próprio código:

  1. Código asynchronous em um aplicativo só pode usar await .
  2. O código do utilitário asynchronous (em uma biblioteca) pode ocasionalmente usar Result / Wait se o código realmente solicitar. Esse uso provavelmente deve ter comentários.
  3. O código de tarefa paralela pode usar Result e Wait .

Note-se que (1) é de longe o caso comum, daí a minha tendência de usar await todos os lugares e tratar os outros casos como exceções à regra geral.

Isso faz sentido se timeoutTask for um produto de Task.Delay , que acredito no que está no livro.

Task.WhenAny retorna Task , onde a tarefa interna é uma das que você passou como argumentos. Pode ser reescrito assim:

 Task anyTask = Task.WhenAny(downloadTask, timeoutTask); await anyTask; if (anyTask.Result == timeoutTask) return null; return downloadTask.Result; 

Em qualquer um dos casos, como o downloadTask já foi concluído, há uma pequena diferença entre o return await downloadTask e o return downloadTask.Result . É porque o último lançará AggregateException que contém qualquer exceção original, como apontado por @KirillShlenskiy nos comentários. O primeiro apenas lançaria novamente a exceção original.

Em ambos os casos, sempre que você manipular exceções, você deve verificar AggregateException e suas exceções internas de qualquer maneira, para obter a causa do erro.