Task.Result est-il le même que .GetAwaiter.GetResult ()?

328

Je lisais récemment du code qui utilise beaucoup de méthodes asynchrones, mais a parfois besoin de les exécuter de manière synchrone. Le code:

Foo foo = GetFooAsync(...).GetAwaiter().GetResult();

Est-ce la même chose que

Foo foo = GetFooAsync(...).Result;
Jay Bazuzi
la source
8
D'après les documents de GetResult: "Ce type et ses membres sont destinés à être utilisés par le compilateur." Une autre personne ne devrait pas l'utiliser.
dépenser le
32
Cela s'appelle "synchronisation sur async", et à moins que vous ne sachiez comment la tâche est mise en œuvre, cela peut être une très mauvaise idée. Il peut instantanément bloquer dans de nombreux cas (une méthode async/ awaitdans MVC, par exemple)
Marc Gravell
14
Dans le monde réel, nous avons des constructeurs, nous avons des interfaces "sans attente" que nous devons implémenter, et on nous donne des méthodes asynchrones partout. Je serais ravi d'utiliser quelque chose qui fonctionne sans, je dois me demander pourquoi il est "dangereux", "à ne pas utiliser" ou "à éviter à tout prix". Chaque fois que je dois jouer avec async, je me sens mal à la tête.
Larry

Réponses:

173

Plutôt. Une petite différence cependant: si l' Taskéchec, GetResult()lance simplement l'exception provoquée directement, tandis que Task.Resultlance un AggregateException. Cependant, quel est l'intérêt d'utiliser l'un ou l'autre de ceux-ci quand c'est async? L'option 100x meilleure est d'utiliser await.

De plus, vous n'êtes pas censé l'utiliser GetResult(). Il est destiné à être utilisé uniquement par le compilateur, pas pour vous. Mais si vous ne voulez pas être ennuyeux AggregateException, utilisez-le.

It'sNotALie.
la source
27
@JayBazuzi Pas si votre framework de tests unitaires prend en charge les tests unitaires asynchrones, ce que je pense que les dernières versions de la plupart des frameworks font.
svick
15
@JayBazuzi: MSTest, xUnit et NUnit prennent tous en charge les async Tasktests unitaires et existent depuis un certain temps maintenant.
Stephen Cleary
18
repousser le 100x - c'est 1000x pire à utiliser attend si vous adaptez l'ancien code et utiliser attendre nécessite une réécriture.
coincé le
13
@AlexZhukovskiy: Je ne suis pas d'accord .
Stephen Cleary
15
The 100x better option is to use await.Je déteste les déclarations comme celle-ci, si je pouvais gifler awaitdevant, je le ferais. Mais, quand je suis en train de récupérer le code async au travail contre le code non comme async ce qui se passe souvent me beaucoup dans Xamarin, je finis par avoir à utiliser des choses comme ContinueWithbeaucoup pour le rendre interblocage pas l'interface utilisateur. Edit: Je sais que c'est vieux, mais cela n'atténue pas ma frustration de trouver des réponses qui disent ceci sans aucune alternative pour les situations où vous ne pouvez pas simplement utiliser await.
Thomas F.18
147

Task.GetAwaiter().GetResult()est préféré Task.Waitet Task.Resultparce qu'il propage des exceptions plutôt que de les encapsuler dans un AggregateException. Cependant, les trois méthodes peuvent entraîner des problèmes de blocage et de famine du pool de threads. Ils devraient tous être évités en faveur de async/await.

La citation ci-dessous explique pourquoi Task.Waitet Task.Resultne contient pas simplement le comportement de propagation d'exception de Task.GetAwaiter().GetResult()(en raison d'une "barre de compatibilité très élevée").

Comme je l'ai mentionné précédemment, nous avons une barre de compatibilité très élevée, et nous avons donc évité de casser les changements. En tant que tel, Task.Waitconserve son comportement d'origine de toujours envelopper. Cependant, vous pouvez vous retrouver dans certaines situations avancées où vous souhaitez un comportement similaire au blocage synchrone utilisé par Task.Wait, mais où vous souhaitez que l'exception d'origine se propage sans être enveloppée plutôt qu'elle ne soit enfermée dans un AggregateException. Pour y parvenir, vous pouvez cibler directement l'attente de la tâche. Lorsque vous écrivez « await task;», le compilateur traduit cela en utilisation de la Task.GetAwaiter()méthode, qui renvoie une instance qui a une GetResult()méthode. Lorsqu'il est utilisé sur une tâche défaillante, GetResult()propage l'exception d'origine (c'est ainsi que « await task;» obtient son comportement). Vous pouvez donc utiliser "task.GetAwaiter().GetResult()»Si vous souhaitez appeler directement cette logique de propagation.

https://blogs.msdn.microsoft.com/pfxteam/2011/09/28/task-exception-handling-in-net-4-5/

" GetResult" Signifie en fait "vérifier la tâche pour les erreurs"

En général, je fais de mon mieux pour éviter le blocage synchrone sur une tâche asynchrone. Cependant, il y a une poignée de situations où je viole cette directive. Dans ces rares conditions, ma méthode préférée est GetAwaiter().GetResult()parce qu'elle préserve les exceptions de tâche au lieu de les envelopper dans un AggregateException.

http://blog.stephencleary.com/2014/12/a-tour-of-task-part-6-results.html

Nitin Agarwal
la source
3
Donc, fondamentalement, Task.GetAwaiter().GetResult()c'est équivalent à await task. Je suppose que la première option est utilisée lorsque la méthode ne peut pas être marquée avec async(constructeur par exemple). Est-ce exact? Si oui, alors il entre en collision avec la meilleure réponse @ It'sNotALie
OlegI
5
@OlegI: Task.GetAwaiter().GetResult()est plus équivalent à Task.Waitet Task.Result(en ce que les trois se bloqueront de manière synchrone et auront le potentiel de blocages), mais Task.GetAwaiter().GetResult()a le comportement de propagation d'exception de la tâche d'attente.
Nitin Agarwal
Ne pouvez-vous pas éviter les blocages dans ce scénario avec (Task) .ConfigureAwait (false) .GetAwaiter (). GetResult (); ?
Daniel Lorenz
3
@DanielLorenz: Voir la citation suivante: "Utiliser ConfigureAwait (false) pour éviter les blocages est une pratique dangereuse. Vous devrez utiliser ConfigureAwait (false) pour chaque attente dans la fermeture transitoire de toutes les méthodes appelées par le code de blocage, y compris toutes les troisièmes - et du code de seconde partie. L'utilisation de ConfigureAwait (false) pour éviter le blocage est au mieux juste un piratage) ... la meilleure solution est "Ne pas bloquer sur le code asynchrone". " - blog.stephencleary.com/2012/07/dont-block-on-async-code.html
Nitin Agarwal
4
Je ne comprends pas. Task.Wait et Task.Result sont cassés par conception? Pourquoi ne sont-ils pas devenus obsolètes?
osexpert
69

https://github.com/aspnet/Security/issues/59

« Une dernière remarque: vous devriez éviter d' utiliser Task.Resultet Task.Waitautant que possible car ils encapsuler toujours l'exception interne dans un AggregateExceptionet remplacer le message par un générique (un ou plusieurs se sont produites des erreurs), ce qui rend le débogage plus difficile même si la version synchrone shouldn. ne pas être utilisé aussi souvent, vous devriez sérieusement envisager de l'utiliser à la Task.GetAwaiter().GetResult()place. "

scyuo
la source
20
La source référencée ici est quelqu'un citant quelqu'un d'autre, sans référence. Considérez le contexte: je peux voir beaucoup de gens aveuglément utiliser GetAwaiter (). GetResult () partout après avoir lu ceci.
Jack Ukleja
2
Nous ne devrions donc pas l'utiliser?
tofutim
11
Si deux tâches se terminent par une exception, vous perdrez la seconde de ce scénario Task.WhenAll(task1, task2).GetAwaiter().GetResult();.
Mgr
Voici un autre exemple: github.com/aspnet/AspNetCore/issues/13611
George Chakhidze
33

Une autre différence est que lorsque la asyncfonction retourne juste Taskau lieu de, Task<T>vous ne pouvez pas utiliser

GetFooAsync(...).Result;

Tandis que

GetFooAsync(...).GetAwaiter().GetResult();

fonctionne encore.

Je sais que l'exemple de code dans la question est pour le cas Task<T>, mais la question est généralement posée.

Nuri Tasdemir
la source
1
Ce n'est pas vrai. Découvrez mon violon qui utilise exactement cette construction: dotnetfiddle.net/B4ewH8
wojciech_rak
3
@wojciech_rak Dans votre code, vous utilisez Resultavec GetIntAsync()qui renvoie Task<int>non seulement Task. Je vous suggère de relire ma réponse.
Nuri Tasdemir
1
Vous avez raison, au début j'ai compris que vous répondiez que vous ne pouvez pas à l' GetFooAsync(...).Result intérieur d' une fonction qui retourne Task. Cela a maintenant un sens, car il n'y a pas de propriétés void en C # ( Task.Resultest une propriété), mais vous pouvez bien sûr appeler une méthode void.
wojciech_rak
22

Comme déjà mentionné si vous pouvez utiliser await. Si vous devez exécuter le code de manière synchrone comme vous le mentionnez .GetAwaiter().GetResult(), .Resultou si vous .Wait()risquez de bloquer, comme beaucoup l'ont dit dans les commentaires / réponses. Comme la plupart d'entre nous aiment les oneliners, vous pouvez les utiliser pour.Net 4.5<

Acquisition d'une valeur via une méthode asynchrone:

var result = Task.Run(() => asyncGetValue()).Result;

Appel synchrone d'une méthode asynchrone

Task.Run(() => asyncMethod()).Wait();

Aucun problème de blocage ne se produira en raison de l'utilisation de Task.Run.

La source:

https://stackoverflow.com/a/32429753/3850405

Ogglas
la source
1

Si une tâche échoue, l'exception est renvoyée lorsque le code de continuation appelle waiter.GetResult (). Plutôt que d'appeler GetResult, nous pourrions simplement accéder à la propriété Result de la tâche. L'avantage d'appeler GetResult est que si la tâche échoue, l'exception est levée directement sans être encapsulée dans AggregateException, ce qui permet des blocs de capture plus simples et plus propres.

Pour les tâches non génériques, GetResult () a une valeur de retour vide. Sa fonction utile est alors uniquement de renvoyer les exceptions.

source: c # 7.0 en bref

Ali Abdollahi
la source