Attendre une tâche terminée comme la tâche.

117

Je lis actuellement " Concurrency in C # Cookbook " de Stephen Cleary, et j'ai remarqué la technique suivante:

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

downloadTaskest un appel à httpclient.GetStringAsync, et timeoutTaskest en cours d'exécution Task.Delay.

Dans le cas où il n'a pas expiré, alors downloadTaskest déjà terminé. Pourquoi est-il nécessaire de faire une seconde attente au lieu de revenir downloadTask.Result, étant donné que la tâche est déjà terminée?

julio.g
la source
3
Il manque un peu de contexte ici, et à moins que les gens aient facilement accès au livre, vous devrez l'inclure. Qu'est-ce que downloadTasket timeoutTask? Que font-ils?
Mike Perrenoud
7
Je ne vois pas de vérification réelle de la réussite ici. La tâche pourrait très bien être mise en défaut, et dans ce cas le comportement sera différent ( AggregateExceptionavec Resultvs première exception via ExceptionDispatchInfoavec await). Discuté plus en détail dans "Task Exception Handling in .NET 4.5" de Stephen Toub: blogs.msdn.com/b/pfxteam/archive/2011/09/28/… )
Kirill Shlenskiy
vous devriez en faire une réponse @KirillShlenskiy
Carsten
@MichaelPerrenoud Vous avez raison, merci d'avoir remarqué, je vais éditer la question.
julio.g

Réponses:

160

Il y a déjà de bonnes réponses / commentaires ici, mais juste pour intervenir ...

Il y a deux raisons pour lesquelles je préfère awaitsur Result(ou Wait). Le premier est que la gestion des erreurs est différente; awaitn'enveloppe pas l'exception dans un fichier AggregateException. Idéalement, le code asynchrone ne devrait jamais avoir à gérer AggregateExceptiondu tout, à moins qu'il ne veuille spécifiquement .

La deuxième raison est un peu plus subtile. Comme je le décris sur mon blog (et dans le livre), Result/ Waitpeut provoquer des blocages , et peut provoquer des blocages encore plus subtils lorsqu'il est utilisé dans une asyncméthode . Ainsi, lorsque je lis du code et que je vois un Resultou Wait, c'est un indicateur d'avertissement immédiat. Le Result/ Waitn'est correct que si vous êtes absolument sûr que la tâche est déjà terminée. Non seulement c'est difficile à voir en un coup d'œil (dans le code du monde réel), mais c'est aussi plus fragile aux changements de code.

Cela ne veut pas dire que Result/ neWait devrait jamais être utilisé. Je suis ces directives dans mon propre code:

  1. Le code asynchrone dans une application ne peut utiliser que await.
  2. Un code utilitaire asynchrone (dans une bibliothèque) peut parfois utiliser Result/ Waitsi le code l'exige vraiment. Un tel usage devrait probablement avoir des commentaires.
  3. Le code de tâche parallèle peut utiliser Resultet Wait.

Notez que (1) est de loin le cas courant, d'où ma tendance à utiliser awaitpartout et à traiter les autres cas comme des exceptions à la règle générale.

Stephen Cleary
la source
Nous avons rencontré le blocage en utilisant 'result' au lieu de 'await' dans nos projets. la partie foirée n'a pas d'erreur de compilation et votre code devient floconneux après un certain temps.
Ahmad Mousavi
@Stephen pourriez-vous s'il vous plaît m'expliquer pourquoi "Idéalement, le code asynchrone ne devrait jamais avoir à gérer AggregateException du tout, à moins qu'il ne le veuille spécifiquement"
vcRobe
4
@vcRobe Car awaitempêche le AggregateExceptionwrapper. AggregateExceptiona été conçu pour la programmation parallèle et non pour la programmation asynchrone.
Stephen Cleary
2
> "Attendre n'est correct que si vous êtes absolument sûr que la tâche est déjà terminée." .... Alors pourquoi ça s'appelle Attendre?
Ryan The Leach
4
@RyanTheLeach: L'objectif initial de Waitétait de se joindre à des instances de parallélisme de tâches dynamiques Task . Il Taskest dangereux de l' utiliser pour attendre des instances asynchrones . Microsoft a envisagé d'introduire un nouveau type «Promise», mais a choisi d'utiliser l'existant à la Taskplace; le compromis de réutiliser le Tasktype existant pour les tâches asynchrones est que vous vous retrouvez avec plusieurs API qui ne devraient tout simplement pas être utilisées dans du code asynchrone.
Stephen Cleary
12

Cela a du sens si timeoutTaskc'est un produit Task.Delaydont je crois ce que c'est dans le livre.

Task.WhenAnyrenvoie Task<Task>, où la tâche interne est l'une de celles que vous avez passées en arguments. Cela pourrait être réécrit comme ceci:

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

Dans les deux cas, car downloadTaska déjà terminé, il y a une très petite différence entre return await downloadTasket return downloadTask.Result. C'est en ce que ce dernier lancera AggregateExceptionce qui encapsulera toute exception d'origine, comme le souligne @KirillShlenskiy dans les commentaires. Le premier renverrait simplement l'exception d'origine.

Dans les deux cas, quel que soit l'endroit où vous gérez des exceptions, vous devez de AggregateExceptiontoute façon rechercher et ses exceptions internes pour trouver la cause de l'erreur.

noseratio
la source