Espera por uma tarefa concluída igual a task.Result?


117

No momento, estou lendo " Concurrency in C # Cookbook ", de Stephen Cleary, e percebi a seguinte técnica:

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

downloadTaské uma chamada para httpclient.GetStringAsynce timeoutTaskestá em execução Task.Delay.

Caso não tenha expirado, então downloadTaskjá está concluído. Por que é necessário fazer uma segunda espera em vez de retornar downloadTask.Result, visto que a tarefa já está concluída?


3
Falta um pouco de contexto aqui e, a menos que as pessoas tenham acesso imediato ao livro, você precisará incluí-lo. O que é downloadTaske timeoutTask? O que eles fazem?
— Mike Perrenoud

7
Não estou vendo uma verificação real de conclusão bem-sucedida aqui. A tarefa pode muito bem apresentar falhas e, nesse caso, o comportamento será diferente ( AggregateExceptioncom Resultvs primeira exceção via ExceptionDispatchInfocom await). Discutido com mais detalhes em "Manipulação de exceções de tarefas no .NET 4.5" de Stephen Toub: blogs.msdn.com/b/pfxteam/archive/2011/09/28/… )
— Kirill Shlenskiy

você deve fazer disso uma resposta @KirillShlenskiy
— Carsten

@MichaelPerrenoud Você está certo, obrigado por notar, vou editar a pergunta.
— julio.g

Respostas:


160

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

Há duas razões pelas quais eu prefiro awaitmais Result(ou Wait). A primeira é que o tratamento de erros é diferente; awaitnão envolve a exceção em um AggregateException. Idealmente, o código assíncrono nunca deveria ter que lidar AggregateException, a menos que especificamente queira .

A segunda razão é um pouco mais sutil. Conforme descrevo em meu blog (e no livro), Result/ Waitpode causar deadlocks e pode causar ainda mais deadlocks sutis quando usado em um asyncmétodo . Portanto, quando estou lendo o código e vejo um Resultou Wait, é um sinalizador de aviso imediato. O Result/ Waitsó está correto se você tiver certeza absoluta de que a tarefa já foi concluída. Não é apenas difícil de ver à primeira vista (no código do mundo real), mas também é mais frágil para alterações de código.

Isso não quer dizer que Result/ nuncaWait deva ser usado. Eu sigo essas diretrizes em meu próprio código:

  1. O código assíncrono em um aplicativo só pode ser usado await.
  2. O código de utilitário assíncrono (em uma biblioteca) pode ocasionalmente usar Result/ Waitse o código realmente exigir. Esse uso provavelmente deve ter comentários.
  3. O código de tarefa paralela pode usar Resulte Wait.

Observe que (1) é de longe o caso comum, daí minha tendência de usar em awaittodos os lugares e tratar os outros casos como exceções à regra geral.


Encontramos o deadlock usando 'result' em vez de 'await' em nossos projetos. a parte complicada é não ter erro de compilação e seu código fica instável depois de um tempo.
— Ahmad Mousavi

@Stephen, você poderia me explicar por que "Idealmente, o código assíncrono nunca deveria ter que lidar com AggregateException, a menos que queira especificamente"
— vcRobe

4
@vcRobe Porque awaitimpede o AggregateExceptioninvólucro. AggregateExceptionfoi projetado para programação paralela, não para programação assíncrona.
— Stephen Cleary de

2
> "A espera está correta apenas se você tiver certeza absoluta de que a tarefa já foi concluída." .... Então por que é chamado Wait?
— Ryan The Leach

4
@RyanTheLeach: O objetivo original de Waitera juntar a instâncias de Paralelismo de Tarefa Dinâmica Task . Usá-lo para esperar por Taskinstâncias assíncronas é perigoso. A Microsoft considerou a introdução de um novo tipo de "Promessa", mas optou por usar o existente Task; a desvantagem de reutilizar o Tasktipo existente para tarefas assíncronas é que você acaba com várias APIs que simplesmente não deveriam ser usadas em código assíncrono.
— Stephen Cleary

12

Isso faz sentido se timeoutTaskfor um produto do Task.Delayqual acredito no que está no livro.

Task.WhenAnyretorna Task<Task>, onde a tarefa interna é uma daquelas que você passou como argumentos. Ele poderia ser reescrito assim:

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

Em ambos os casos, porque downloadTaskjá foi concluído, há uma diferença muito pequena entre return await downloadTaske return downloadTask.Result. É que o último irá lançar o AggregateExceptionque envolve qualquer exceção original, como apontado por @KirillShlenskiy nos comentários. O primeiro iria apenas relançar a exceção original.

Em qualquer um dos casos, sempre que você lida com exceções, deve verificar AggregateExceptionsuas exceções internas de qualquer maneira, para chegar à causa do erro.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.