web-dev-qa-db-fra.com

Exécuter plusieurs tâches asynchrones et attendre qu'elles soient toutes terminées

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

Il y a beaucoup d'articles, mais je semble devenir de plus en 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 de manière à ce qu’elles commencent après l’achèvement 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 soient exécutées 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?

227
Daniel Minnaar

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

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

await Task.WhenAll(task1, task2);
_

La principale différence entre Task.WaitAll et _Task.WhenAll_ est que le premier bloquera (comme si vous utilisiez Wait sur une seule tâche), tandis que le dernier ne bloquerait pas et ne pourrait pas attendu, cédant le contrôle à l'appelant jusqu'à la fin de toutes les tâches.

De plus, 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é générée lors de l'exécution d'au moins une des instances de tâche. Si une tâche a été annulée, AggregateException contient une exception 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 Faulted, où ses exceptions contiendront l'agrégation de l'ensemble des exceptions non enveloppées de chacune des tâches fournies.

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

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

375
Yuval Itzchakov

Vous pouvez créer plusieurs tâches telles que:

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

Task.WaitAll(TaskList.ToArray());
95
Virus

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 ça:

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();
});
23
me22

Vous pouvez utiliser WhenAll pour renvoyer un Task en attente ou WaitAll qui n'a pas de type de retour et bloquera toute exécution de code similaire à Thread.Sleep jusqu'à ce que toutes les tâches soient terminées, annulées ou défaillantes.

enter image description here

Exemple

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

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

Si vous voulez exécuter les tâches dans un ordre pratique, vous pouvez obtenir une forme d’inspiration this .

21
NtFreX

Voulez-vous chaîner les Tasks, ou peuvent-ils être invoqués de manière parallèle?

Pour chaîner
Faites juste quelque chose comme

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

et n'oubliez pas de vérifier la précédente instance Task dans chaque ContinueWith car elle pourrait être défaillante.

Pour la manière parallèle
La méthode la plus simple que j'ai rencontrée: Parallel.Invoke Sinon, il y a Task.WaitAll ou vous pouvez même utiliser WaitHandles pour faire un compte à rebours Il ne reste aucune action (attendez, il y a une nouvelle classe: CountdownEvent ), ou ...

8
Andreas Niedermair

Encore une autre réponse ... mais je me trouve généralement dans un cas où je dois charger des données simultanément et les mettre dans des variables, telles que:

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
}
4
Yehor Hromadskyi

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
4
DalSoft