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?
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.
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());
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();
});
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.
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 .
Voulez-vous chaîner les Task
s, 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 WaitHandle
s pour faire un compte à rebours Il ne reste aucune action (attendez, il y a une nouvelle classe: CountdownEvent
), ou ...
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
}
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