Exécuter plusieurs tâches asynchrones et attendre qu'elles se terminent toutes

265

J'ai besoin d'exécuter plusieurs tâches asynchrones dans une application console et d'attendre qu'elles se terminent toutes avant de poursuivre le traitement.

Il y a beaucoup d'articles là-bas, mais je semble devenir plus confus à mesure que je lis. J'ai lu et compris les principes de base de la bibliothèque de tâches, mais il me manque clairement un lien quelque part.

Je comprends qu'il est possible d'enchaîner les tâches pour qu'elles commencent après la fin d'une autre (ce qui est à peu près le scénario de tous les articles que j'ai lus), mais je veux que toutes mes tâches s'exécutent en même temps, et je veux le savoir une fois ils sont tous terminés.

Quelle est la mise en œuvre la plus simple pour un scénario comme celui-ci?

Daniel Minnaar
la source

Réponses:

441

Les deux réponses ne mentionnent pas l'attente Task.WhenAll:

var task1 = DoWorkAsync();
var task2 = DoMoreWorkAsync();

await Task.WhenAll(task1, task2);

La principale différence entre Task.WaitAllet Task.WhenAllest que le premier bloquera (similaire à l'utilisation Waitsur une seule tâche) tandis que le second ne sera pas et peut être attendu, ce qui rend le contrôle à l'appelant jusqu'à ce que toutes les tâches soient terminées.

Plus encore, la gestion des exceptions diffère:

Task.WaitAll:

Au moins une des instances de tâche a été annulée - ou - une exception a été levée lors de l'exécution d'au moins une des instances de tâche. Si une tâche a été annulée, l'AggregateException contient une OperationCanceledException dans sa collection InnerExceptions.

Task.WhenAll:

Si l'une des tâches fournies se termine dans un état défaillant, la tâche renvoyée se terminera également dans un état défaillant, où ses exceptions contiendront l'agrégation de l'ensemble des exceptions non encapsulées de chacune des tâches fournies.

Si aucune des tâches fournies n'est défaillante mais qu'au moins l'une d'entre elles a été annulée, la tâche renvoyée se terminera à l'état Annulé.

Si aucune des tâches n'est défaillante et qu'aucune des tâches n'a été annulée, la tâche résultante se terminera à l'état RanToCompletion. Si le tableau / énumérateur fourni ne contient aucune tâche, la tâche renvoyée passera immédiatement à un état RanToCompletion avant d'être renvoyée à l'appelant.

Yuval Itzchakov
la source
4
Lorsque j'essaie, mes tâches s'exécutent séquentiellement? Doit-on commencer chaque tâche individuellement avant await Task.WhenAll(task1, task2);?
Zapnologica
4
@Zapnologica Task.WhenAllne démarre pas les tâches pour vous. Vous devez les fournir "à chaud", ce qui signifie déjà commencé.
Yuval Itzchakov
2
D'accord. Ça a du sens. Alors, que fera votre exemple? Parce que vous ne les avez pas démarrés?
Zapnologica
2
@YuvalItzchakov merci beaucoup! C'est tellement simple mais ça m'a beaucoup aidé aujourd'hui! Vaut au moins +1000 :)
Daniel Dušek
1
@Pierre, je ne suis pas en train de suivre. Qu'est-ce que la StartNewrotation de nouvelles tâches a à voir avec l'attente asynchrone de toutes?
Yuval Itzchakov
106

Vous pouvez créer de nombreuses tâches comme:

List<Task> TaskList = new List<Task>();
foreach(...)
{
   var LastTask = new Task(SomeFunction);
   LastTask.Start();
   TaskList.Add(LastTask);
}

Task.WaitAll(TaskList.ToArray());
Virus
la source
48
Je recommanderais WhenAll
Ravi
Est-il possible de démarrer plusieurs nouveaux threads, en même temps, en utilisant le mot-clé wait plutôt que .Start ()?
Matt W
1
@MattW Non, lorsque vous utilisez attendre, il attendra qu'il se termine. Dans ce cas, vous ne pourriez pas créer un environnement multithread. C'est la raison pour laquelle toutes les tâches sont attendues à la fin de la boucle.
Virus du
5
Downvote pour les futurs lecteurs car il n'est pas précisé qu'il s'agit d'un appel bloquant.
JRoughan
Voir la réponse acceptée pour les raisons de ne pas le faire.
EL MOJO
26

La meilleure option que j'ai vue est la méthode d'extension suivante:

public static Task ForEachAsync<T>(this IEnumerable<T> sequence, Func<T, Task> action) {
    return Task.WhenAll(sequence.Select(action));
}

Appelez ça comme ceci:

await sequence.ForEachAsync(item => item.SomethingAsync(blah));

Ou avec un lambda asynchrone:

await sequence.ForEachAsync(async item => {
    var more = await GetMoreAsync(item);
    await more.FrobbleAsync();
});
me22
la source
26

Vous pouvez utiliser WhenAllce qui retournera un fichier attendu Taskou WaitAllqui n'a pas de type de retour et bloquera l'exécution du code jusqu'à Thread.Sleepce que toutes les tâches soient terminées, annulées ou en panne.

entrez la description de l'image ici

Exemple

var tasks = new Task[] {
    TaskOperationOne(),
    TaskOperationTwo()
};

Task.WaitAll(tasks);
// or
await Task.WhenAll(tasks);

Si vous souhaitez exécuter les tâches dans un ordre pratique, vous pouvez vous inspirer de cette réponse.

NtFreX
la source
désolé d'être venu en retard à la fête, mais pourquoi avez-vous awaitpour chaque opération et en même temps utiliser WaitAllou WhenAll. Les tâches d' Task[]initialisation ne devraient-elles pas être sansawait ?
dee zg
@dee zg Vous avez raison. L'attente ci-dessus va à l'encontre du but. Je vais changer ma réponse et les supprimer.
NtFreX
Oui c'est ça. Merci pour la clarification! (vote positif pour une bonne réponse)
dee zg
8

Voulez-vous enchaîner les Tasks ou pouvez-vous les invoquer de manière parallèle?

Pour enchaîner Faites
juste quelque chose comme

Task.Run(...).ContinueWith(...).ContinueWith(...).ContinueWith(...);
Task.Factory.StartNew(...).ContinueWith(...).ContinueWith(...).ContinueWith(...);

et n'oubliez pas de vérifier l' Taskinstance précédente dans chacune ContinueWithcar elle pourrait être défectueuse.

Pour la manière parallèle
La méthode la plus simple que j'ai rencontrée:Parallel.Invoke sinon il y a Task.WaitAllou vous pouvez même utiliser WaitHandles pour faire un compte à rebours jusqu'à zéro actions (attendez, il y a une nouvelle classe:) CountdownEvent, ou ...

Andreas Niedermair
la source
3
Appréciez la réponse, mais vos suggestions auraient pu être expliquées un peu plus.
Daniel Minnaar
@drminnaar de quelle autre explication à côté des liens vers msdn avec des exemples avez-vous besoin? vous n'avez même pas cliqué sur les liens, n'est-ce pas?
Andreas Niedermair
4
J'ai cliqué sur les liens et j'ai lu le contenu. J'allais pour l'Invoke, mais il y avait beaucoup d'If et Buts sur son fonctionnement asynchrone ou non. Vous modifiez votre réponse en continu. Le lien WaitAll que vous avez publié était parfait, mais je suis allé chercher la réponse qui a démontré la même fonctionnalité d'une manière plus rapide et plus facile à lire. Ne vous offusquez pas, votre réponse offre toujours de bonnes alternatives à d'autres approches.
Daniel Minnaar
@drminnaar aucune infraction prise ici, je suis juste curieux :)
Andreas Niedermair
5

Voici comment je le fais avec un tableau Func <> :

var tasks = new Func<Task>[]
{
   () => myAsyncWork1(),
   () => myAsyncWork2(),
   () => myAsyncWork3()
};

await Task.WhenAll(tasks.Select(task => task()).ToArray()); //Async    
Task.WaitAll(tasks.Select(task => task()).ToArray()); //Or use WaitAll for Sync
DalSoft
la source
1
Pourquoi ne le gardez-vous pas simplement comme tableau de tâches?
Talha Talip Açıkgöz
1
Si vous ne faites pas attention @ talha-talip-açıkgöz, vous exécutez les tâches lorsque vous ne vous attendiez pas à ce qu'elles s'exécutent. Le faire en tant que délégué Func rend votre intention claire.
DalSoft
5

Encore une autre réponse ... mais je me retrouve généralement dans un cas, quand j'ai besoin de charger des données simultanément et de les mettre en variables, comme:

var cats = new List<Cat>();
var dog = new Dog();

var loadDataTasks = new Task[]
{
    Task.Run(async () => cats = await LoadCatsAsync()),
    Task.Run(async () => dog = await LoadDogAsync())
};

try
{
    await Task.WhenAll(loadDataTasks);
}
catch (Exception ex)
{
    // handle exception
}
Yehor Hromadskyi
la source
1
Si LoadCatsAsync()et ne LoadDogAsync()sont que des appels de base de données, ils sont liés aux E / S. Task.Run()est pour le travail lié au CPU; cela ajoute une surcharge supplémentaire inutile si tout ce que vous faites est d'attendre une réponse du serveur de base de données. La réponse acceptée par Yuval est la bonne façon pour le travail lié aux E / S.
Stephen Kennedy
@StephenKennedy pourriez-vous s'il vous plaît préciser quel type de frais généraux et dans quelle mesure cela peut avoir un impact sur les performances? Merci!
Yehor Hromadskyi
Ce serait assez difficile à résumer dans la boîte de commentaires :) Au lieu de cela, je recommande de lire les articles de Stephen Cleary - c'est un expert dans ce domaine. Commencez ici: blog.stephencleary.com/2013/10/…
Stephen Kennedy
-1

J'ai préparé un morceau de code pour vous montrer comment utiliser la tâche pour certains de ces scénarios.

    // method to run tasks in a parallel 
    public async Task RunMultipleTaskParallel(Task[] tasks) {

        await Task.WhenAll(tasks);
    }
    // methode to run task one by one 
    public async Task RunMultipleTaskOneByOne(Task[] tasks)
    {
        for (int i = 0; i < tasks.Length - 1; i++)
            await tasks[i];
    }
    // method to run i task in parallel 
    public async Task RunMultipleTaskParallel(Task[] tasks, int i)
    {
        var countTask = tasks.Length;
        var remainTasks = 0;
        do
        {
            int toTake = (countTask < i) ? countTask : i;
            var limitedTasks = tasks.Skip(remainTasks)
                                    .Take(toTake);
            remainTasks += toTake;
            await RunMultipleTaskParallel(limitedTasks.ToArray());
        } while (remainTasks < countTask);
    }
sayah imad
la source
1
comment obtenir les résultats des tâches? Par exemple, pour fusionner des "lignes" (à partir de N tâches en parallèle) dans une table de données et les lier à gridview asp.net?
PreguntonCojoneroCabrón