Comment attendre une liste de tâches de manière asynchrone avec LINQ?

J’ai une liste de tâches que j’ai créées comme ceci:

public async Task<IList> GetFoosAndDoSomethingAsync() { var foos = await GetFoosAsync(); var tasks = foos.Select(async foo => await DoSomethingAsync(foo)).ToList(); ... } 

En utilisant .ToList() , les tâches devraient toutes commencer. Maintenant, je veux attendre leur achèvement et retourner les résultats.

Cela fonctionne dans le bloc ci-dessus ...

 var list = new List(); foreach (var task in tasks) list.Add(await task); return list; 

Il fait ce que je veux, mais cela semble plutôt maladroit. Je préfère plutôt écrire quelque chose de plus simple comme ceci:

 return tasks.Select(async task => await task).ToList(); 

… mais cela ne comstack pas. Qu’est-ce que je rate? Ou est-il simplement impossible d’exprimer les choses de cette façon?

LINQ ne fonctionne pas parfaitement avec le code async , mais vous pouvez le faire:

 var tasks = foos.Select(DoSomethingAsync).ToList(); await Task.WhenAll(tasks); 

Si vos tâches renvoient toutes le même type de valeur, vous pouvez même le faire:

 var results = await Task.WhenAll(tasks); 

ce qui est plutôt sympa. WhenAll renvoie un tableau, donc je pense que votre méthode peut renvoyer directement les résultats:

 return await Task.WhenAll(tasks); 

Pour développer la réponse de Stephen, j’ai créé la méthode d’extension suivante pour conserver le style fluide de LINQ. Vous pouvez alors faire

 await someTasks.WhenAll() namespace System.Linq { public static class IEnumerableExtensions { public static Task WhenAll(this IEnumerable> source) { return Task.WhenAll(source); } } } 

Utilisez Task.WaitAll ou Task.WhenAll plus approprié.

Task.WhenAll devrait faire le tour ici.

Un problème avec Task.WhenAll est que cela créerait un parallélisme. Dans la plupart des cas, cela pourrait être encore mieux, mais parfois vous voulez l’éviter. Par exemple, lire des données par lots à partir de la firebase database et envoyer des données à un service Web distant. Vous ne voulez pas charger tous les lots dans la mémoire mais bash la firebase database une fois que le lot précédent a été traité. Donc, vous devez casser l’asynchronicité. Voici un exemple:

 var events = Enumerable.Range(0, totalCount/ batchSize) .Select(x => x*batchSize) .Select(x => dbRepository.GetEventsBatch(x, batchSize).GetAwaiter().GetResult()) .SelectMany(x => x); foreach (var carEvent in events) { } 

Remarque: GetAwaiter (). GetResult () le convertit en synchronisation. DB serait frappé paresseusement seulement une fois que batchSize d’événements ont été traités.