Le code suivant démarre T1Async () , T2Async () , T3Async () , T4Async () et attendez qu'ils se terminent tous avant de consommer les résultats de ces appels. Cependant, le temps d'exécution de ces appels asynchrones peut être court ou très long de manière aléatoire.
Task<int> T1Async() { };
Task<string> T2Async() { };
Task<ClassX> T3Async() { };
Task<ClassY> T4Async() { };
var t1 = T1Async();
var t2 = T2Async();
var t3 = T3Async();
var t4 = T4Async();
var v1 = await t1;
var v2 = await t2;
var v3 = await t3;
var v4 = await t4;
// Or Task.WhenAll(new Task[] { t1, t2, t3, t4 });
UseV1(v1); // Want these calls be run asap
UseV2(v2);
UseV3(v3);
UseV4(v4);
UseAll(v1, v2, v3, v4); // Need all the result here
Comment consommer les résultats dès que l'appel est terminé et s'assurer que tous les résultats sont consommés?
3 Réponses :
Vous souhaitez donc exécuter les tâches UseVX en parallèle mais attendre tous les résultats (de type différent) afin de les transmettre à la méthode UseAll .
Utilisation de Task.Run
var t1 = T1().ContinueWith(x => { UseV1(x.Result); return x.Result; });
var t2 = T2().ContinueWith(x => { UseV2(x.Result); return x.Result; });
var t3 = T3().ContinueWith(x => { UseV3(x.Result); return x.Result; });
var t4 = T4().ContinueWith(x => { UseV4(x.Result); return x.Result; });
await Task.WhenAll(t1, t2, t3, t4);
UseAll(t1.Result, t2.Result, t3.Result, t4.Result);
Utilisation de ContinueWith
var t1 = Task.Run(async () => { var x = await T1(); UseV1(x); return x; });
var t2 = Task.Run(async () => { var x = await T2(); UseV2(x); return x; });
var t3 = Task.Run(async () => { var x = await T3(); UseV3(x); return x; });
var t4 = Task.Run(async () => { var x = await T4(); UseV4(x); return x; });
await Task.WhenAll(t1, t2, t3, t4);
UseAll(t1.Result, t2.Result, t3.Result, t4.Result);
Pour exécuter du code asynchrone simultanément, utilisez Task.WhenAll . Pour exécuter une méthode à la fin de chaque opération, introduisez une nouvelle méthode async :
async Task<int> T1AndUseV1Async() { var v1 = await T1Async(); UseV1(v1); return v1; };
async Task<string> T2AndUseV2Async() { var v2 = await T2Async(); UseV2(v2); return v2; };
async Task<ClassX> T3AndUseV3Async() { var v3 = await T3Async(); UseV3(v3); return v3; };
async Task<ClassY> T4AndUseV4Async() { var v4 = await T4Async(); UseV4(v4); return v4; };
var t1 = T1AndUseV1Async();
var t2 = T2AndUseV2Async();
var t3 = T3AndUseV3Async();
var t4 = T4AndUseV4Async();
await Task.WhenAll(t1, t2, t3, t4);
UseAll(await t1, await t2, await t3, await t4);
Il est étrange de voir du code en attente d'une tâche qui était déjà attendue. Une raison pour laquelle vous préférez attendre ici?
@huysentruitw: Oui; si un futur responsable décide que wait Task.WhenAll est inutile (une question d'opinion) et le supprime, alors utiliser wait plutôt que Result empêcher les wrappers AggregateException .
Oui, mais c'est aussi le travail du responsable de changer .Result en wait dans ce cas. Je conviens que tous les responsables ne sont pas conscients de cela, mais celui qui décide de Task.WhenAll n'est plus nécessaire, du moins devrait l'être :)
@huysentruitw: En théorie, oui; en pratique, certainement pas. De plus, dans le code du monde réel, le Task.WhenAll et le await ne sont pas toujours aussi proches l'un de l'autre. Permettez-moi de contourner la question: il est étrange de voir Task.Result utilisé dans du code asynchrone. Une raison pour laquelle vous préférez Résultat ici?
Parce que c'est le seul cas dans lequel j'utilise réellement .Result. Pour moi, cela clarifie le fait que nous sommes sûrs que la tâche est terminée. J'ai peur qu'en utilisant await dans ce cas, le compilateur ajoute beaucoup de ballonnements indésirables.
Il n'y a aucun impact sur l'exécution. Le code généré est un peu plus long pour wait , mais je pense que c'est un petit prix à payer pour un code (légèrement) plus maintenable.
Un cas testable:
async Task<int> T1() { return await Task.FromResult(1); }
async Task<string> T2() { return await Task.FromResult("T2"); }
async Task<char> T3() { await Task.Delay(2000); return await Task.FromResult('A'); }
async Task<string> T4() { return await Task.FromResult("T4"); }
var t1 = T1().ContinueWith(x => { Console.WriteLine($"Use T1: {x.Result}"); return x.Result; });
var t2 = T2().ContinueWith(x => { Console.WriteLine($"Use T2: {x.Result}"); return x.Result; });
var t3 = T3().ContinueWith(x => { Console.WriteLine($"Use T3: {x.Result}"); return x.Result; });
var t4 = T4().ContinueWith(x => { Console.WriteLine($"Use T4: {x.Result}"); return x.Result; });
await Task.WhenAll(t1, t2, t3, t4);
Console.WriteLine("Done");
Task.WhenAll ()vous aidera, consultez également ce fil de discussion stackoverflow.com/questions/18310996/...Pouvez-vous clarifier ce que vous entendez par
Utiliser (...) n'a besoin que du résultat d'un seul appel? Cela pourrait être la différence entre l'utilisation deTask.WhenAnyetTask.WhenAllEst-il sûr de traiter les résultats en parallèle?
@ColinM,
Use ()n'a besoin que d'un seul résultat de l'un de ces appels asynchrones.@huysentruitw, oui pour certains traitements intermédiaires. Finalement, j'ai besoin du résultat de tout pour faire autre chose.
Donc, votre première chose à faire serait d'utiliser
Task.WhenAnypour récupérer la première tâche terminée et de la transmettre àUse, ensuite, en fonction de ce que vous avez dit dans le commentaires, utilisezTask.WhenAllpour attendre la fin des autres tâches pourUseAlldans chaque définition de TNAsync (), ne pouvez-vous pas simplement inclure Use (...) comme instruction finale? c'est-à-dire, Task T1Async () {/ * faites ce que vous aviez initialement prévu ici, puis * / Use (....)}; Faites de même pour chacune de vos définitions de tâche, puis placez un WhenAll () juste avant UseAll (....) à la fin. De cette façon, le Use () correspondant au résultat de chaque tâche pourra s'exécuter dès que le reste du code de la tâche sera terminé, et vous permettra encore d'attendre qu'ils se terminent tous avant de passer à UseAll ();
Pour revenir à la question de @ RichardII, pourquoi ne pouvez-vous pas gérer l'appel de la méthode
UseVxdans la tâche?@huysentruitw, la question que vous avez posée ne traite pas le résultat dès qu'il se termine?
AFAIK Je n'ai pas posé de question. Si vous faites référence à la réponse, oui, elle a été traitée dès qu'elle est terminée, en parallèle.
La réponse acceptée utilise
await Task.WhenAll (catTask, houseTask, carTask);qui attendra la fin des appels? Je veux consommer le résultat si l'un d'entre eux se termine.Ah vous parlez du duplicata. Ouais, il faudra peut-être peaufiner ce que vous voulez. Permettez-moi de faire encore un effort pour répondre à votre question, mais ne changez pas les règles cette fois :)
Si vous souhaitez exécuter une tâche immédiatement après chaque exécution, pourquoi le faire de manière asynchrone? On dirait que vous pouvez simplement ajouter ce suivi à chaque tâche asynchrone, puis les attendre tous pour terminer le travail final.
@Nat, dans ma situation réelle, dépend de certaines conditions, je peux avoir besoin d'exécuter, par exemple, la tâche 2, 4 avant la tâche 1, 3. Je ne peux pas simplement ajouter ces appels dans les appels asynchrones encapsulés.
Je ne comprends pas précisément ce que vous essayez de faire, mais si vous essayez d'implémenter une logique plus complexe, vous devrez alors écrire un répartiteur. Ajoutez chaque tâche asynchrone avec un rappel au répartiteur, puis le répartiteur peut décider de ce qu'il doit faire, le cas échéant, une fois chaque tâche terminée.
Oui, un répartiteur devrait fonctionner. Y a-t-il un exemple?
@ ca9163d9 Beaucoup d'exemples de répartiteurs, même si vous voudrez probablement poser cela comme une question distincte afin que vous puissiez obtenir un exemple correspondant à votre cas d'utilisation. La réponse acceptée semble résoudre le problème que vous posiez dans la question ci-dessus, donc cela semble être la voie à suivre si c'est tout ce dont vous aviez besoin. Si vous avez une logique plus complexe à distribuer, vous voudrez être clair sur ce que c'est.