J'ai 3 tâches:
private async Task<Cat> FeedCat() {}
private async Task<House> SellHouse() {}
private async Task<Tesla> BuyCar() {}
Ils doivent tous s'exécuter avant que mon code puisse continuer et j'ai également besoin des résultats de chacun. Aucun des résultats n'a rien en commun les uns avec les autres
Comment puis-je appeler et attendre la fin des 3 tâches, puis obtenir les résultats?
c#
.net
async-await
task-parallel-library
.net-4.5
Ian Vink
la source
la source
Réponses:
Après utilisation
WhenAll
, vous pouvez extraire les résultats individuellement avecawait
:Vous pouvez également utiliser
Task.Result
(puisque vous savez à ce stade, ils ont tous réussi). Cependant, je recommande d'utiliserawait
car c'est clairement correct, alors que celaResult
peut causer des problèmes dans d'autres scénarios.la source
WhenAll
complètement le de ceci; les attentes veilleront à ce que vous ne dépassiez pas les 3 affectations ultérieures tant que les tâches ne sont pas terminées.Task.WhenAll()
permet d'exécuter la tâche en mode parallèle . Je ne comprends pas pourquoi @Servy a suggéré de le supprimer. Sans cela,WhenAll
ils seront exécutés un par uncatTask
est déjà en cours d'exécution au moment de son retourFeedCat
. Donc, l'une ou l'autre approche fonctionnera - la seule question est de savoir si vous voulez les utiliserawait
une à la fois ou toutes ensemble. La gestion des erreurs est légèrement différente - si vous utilisezTask.WhenAll
, alors tout le monde le feraawait
, même si l'un d'eux échoue tôt.WhenAll
n'a aucun impact sur le moment où les opérations s'exécutent, ni sur la façon dont elles s'exécutent. Il ne dispose d' aucune possibilité d'effectuer la façon dont les résultats sont observés. Dans ce cas particulier, la seule différence est qu'une erreur dans l'une des deux premières méthodes entraînerait la levée de l'exception dans cette pile d'appels plus tôt dans ma méthode que celle de Stephen (bien que la même erreur soit toujours levée, s'il y en a) ).Juste
await
les trois tâches séparément, après les avoir toutes démarrées.la source
Task.WhenAll
change littéralement rien au comportement du programme, de quelque manière que ce soit. Il s'agit d'un appel de méthode purement redondant. Vous pouvez l'ajouter, si vous le souhaitez, comme choix esthétique, mais cela ne change pas ce que fait le code. Le temps d'exécution du code sera identique avec ou sans cet appel de méthode (eh bien, techniquement, il y aura une très petite surcharge pour appelerWhenAll
, mais cela devrait être négligeable), ce qui rend cette version légèrement plus longue à exécuter que cette version.WhenAll
est un changement purement esthétique. La seule différence de comportement observable est de savoir si vous attendez que les tâches ultérieures se terminent en cas de défaillance d'une tâche antérieure, ce qui n'est généralement pas nécessaire. Si vous ne croyez pas aux nombreuses explications pour lesquelles votre déclaration n'est pas vraie, vous pouvez simplement exécuter le code par vous-même et voir que ce n'est pas vrai.Si vous utilisez C # 7, vous pouvez utiliser une méthode d'emballage pratique comme celle-ci ...
... pour activer une syntaxe pratique comme celle-ci lorsque vous souhaitez attendre plusieurs tâches avec différents types de retour. Il faudrait bien sûr faire plusieurs surcharges pour différents nombres de tâches à attendre.
Cependant, consultez la réponse de Marc Gravell pour quelques optimisations autour de ValueTask et des tâches déjà terminées si vous avez l'intention de transformer cet exemple en quelque chose de réel.
la source
Task.WhenAll()
ne retourne pas de tuple. L'une est en cours de construction à partir desResult
propriétés des tâches fournies une fois la tâche renvoyée parTask.WhenAll()
terminée..Result
appels selon le raisonnement de Stephen pour éviter que d'autres personnes ne perpétuent la mauvaise pratique en copiant votre exemple.Étant donné trois tâches -
FeedCat()
,SellHouse()
etBuyCar()
, il y a deux cas intéressants: soit ils se terminent tous de manière synchrone (pour une raison quelconque, peut-être la mise en cache ou une erreur), soit ils ne le font pas.Disons que nous avons, à partir de la question:
Maintenant, une approche simple serait:
mais ... ce n'est pas pratique pour traiter les résultats; nous voudrions généralement
await
:mais cela fait beaucoup de surcharge et alloue divers tableaux (y compris le
params Task[]
tableau) et listes (en interne). Cela fonctionne, mais ce n'est pas génial IMO. À bien des égards, il est plus simple d'utiliser uneasync
opération etawait
chacune à son tour:Contrairement à certains des commentaires ci-dessus, l'utilisation
await
au lieu de neTask.WhenAll
fait aucune différence sur la façon dont les tâches s'exécutent (simultanément, séquentiellement, etc.). Au plus haut niveau, ilTask.WhenAll
est antérieur au bon support du compilateur pourasync
/await
et était utile lorsque ces choses n'existaient pas . Il est également utile lorsque vous avez un tableau arbitraire de tâches, plutôt que 3 tâches discrètes.Mais: nous avons toujours le problème que
async
/await
génère beaucoup de bruit de compilation pour la suite. S'il est probable que les tâches puissent effectivement se terminer de manière synchrone, nous pouvons optimiser cela en créant un chemin synchrone avec une solution de secours asynchrone:Cette approche de "chemin de synchronisation avec repli asynchrone" est de plus en plus courante, en particulier dans le code haute performance où les exécutions synchrones sont relativement fréquentes. Notez que cela n'aidera pas du tout si l'achèvement est toujours véritablement asynchrone.
Autres choses qui s'appliquent ici:
avec C # récent, un modèle commun est que la
async
méthode de secours est généralement implémentée en tant que fonction locale:préfèrent
ValueTask<T>
àTask<T>
s'il y a une bonne chance de choses jamais tout à fait synchrone avec beaucoup de différentes valeurs de retour:si possible, préférer
IsCompletedSuccessfully
àStatus == TaskStatus.RanToCompletion
; cela existe maintenant dans .NET Core pourTask
, et partout pourValueTask<T>
la source
Task
quand ils sont tous terminés sans utiliser les résultats.await
obtenir la "meilleure" sémantique des exceptions, en supposant que les exceptions sont rares mais significativesVous pouvez les stocker dans des tâches, puis les attendre tous:
la source
var catTask = FeedCat()
la fonctionFeedCat()
et ne stocke pas le résultat pourcatTask
rendre leawait Task.WhenAll()
type de pièce inutile car la méthode a déjà été exécutée ??Dans le cas où vous essayez de consigner toutes les erreurs, assurez-vous de conserver la ligne Task.WhenAll dans votre code, de nombreux commentaires suggèrent que vous pouvez le supprimer et attendre des tâches individuelles. Task.WhenAll est vraiment important pour la gestion des erreurs. Sans cette ligne, vous laissez potentiellement votre code ouvert pour les exceptions non observées.
Imagine FeedCat lève une exception dans le code suivant:
Dans ce cas, vous n'attendrez jamais sur houseTask ni carTask. Il y a 3 scénarios possibles ici:
SellHouse est déjà terminé avec succès lorsque FeedCat a échoué. Dans ce cas, tout va bien.
SellHouse n'est pas terminée et échoue avec exception à un moment donné. Aucune exception n'est observée et sera renvoyée sur le thread du finaliseur.
SellHouse n'est pas terminée et contient des attentes à l'intérieur. Dans le cas où votre code s'exécute dans ASP.NET SellHouse échouera dès que certaines des attentes seront terminées à l'intérieur. Cela se produit car vous avez essentiellement déclenché et oublié le contexte d'appel et de synchronisation a été perdu dès que FeedCat a échoué.
Voici l'erreur que vous obtiendrez pour le cas (3):
Pour le cas (2), vous obtiendrez une erreur similaire mais avec une trace de pile d'exceptions d'origine.
Pour .NET 4.0 et versions ultérieures, vous pouvez intercepter les exceptions non observées à l'aide de TaskScheduler.UnobservedTaskException. Pour .NET 4.5 et les exceptions non observées ultérieures sont avalées par défaut pour .NET 4.0, l'exception non observée plantera votre processus.
Plus de détails ici: Gestion des exceptions de tâches dans .NET 4.5
la source
Vous pouvez utiliser
Task.WhenAll
comme mentionné, ouTask.WaitAll
, selon que vous souhaitez que le thread attende. Jetez un œil au lien pour une explication des deux.WaitAll vs WhenAll
la source
Utilisez
Task.WhenAll
puis attendez les résultats:la source
Avertissement avant
Juste un bref avertissement à ceux qui visitent ce sujet et d'autres threads similaires à la recherche d'un moyen de paralléliser EntityFramework en utilisant async + attendre + ensemble d'outils de tâche : le modèle montré ici est bon, cependant, quand il s'agit du flocon de neige spécial d'EF, vous ne le ferez pas réaliser une exécution parallèle à moins que et jusqu'à ce que vous utilisiez une (nouvelle) instance de contexte db distincte à l'intérieur de chaque appel * Async () impliqué.
Ce genre de chose est nécessaire en raison des limitations de conception inhérentes aux contextes ef-db qui interdisent d'exécuter plusieurs requêtes en parallèle dans la même instance de contexte ef-db.
En capitalisant sur les réponses déjà données, c'est le moyen de s'assurer que vous collectez toutes les valeurs même dans le cas où une ou plusieurs des tâches entraînent une exception:
Une implémentation alternative qui a plus ou moins les mêmes caractéristiques de performance pourrait être:
la source
si vous souhaitez accéder à Cat, procédez comme suit:
C'est très simple à faire et très utile à utiliser, il n'est pas nécessaire de rechercher une solution complexe.
la source
dynamic
c'est le diable. C'est pour l'interopérabilité COM délicate et autres, et ne doit pas être utilisé dans toutes les situations où il n'est pas absolument nécessaire. Surtout si vous vous souciez de la performance. Ou tapez sécurité. Ou refactoring. Ou débogage.