J'ai une liste de tâches que j'ai créées comme ceci:
public async Task<IList<Foo>> 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 renvoyer les résultats.
Cela fonctionne dans le ...
bloc ci-dessus :
var list = new List<Foo>();
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 de loin écrire quelque chose de plus simple comme celui-ci:
return tasks.Select(async task => await task).ToList();
... mais cela ne compile pas. Qu'est-ce que je rate? Ou n'est-il tout simplement pas possible d'exprimer les choses de cette façon?
c#
linq
async-await
Matt Johnson-Pint
la source
la source
DoSomethingAsync(foo)
série pour chaque toto, ou s'agit-il d'un candidat pour Parallel.ForEach <Foo> ?Parallel.ForEach
bloque. Le modèle ici vient de la vidéo Asynchronous C # de Jon Skeet sur Pluralsight . Il s'exécute en parallèle sans blocage..ToList()
si je vais simplement l'utiliserWhenAll
.)DoSomethingAsync
est écrite, la liste peut être exécutée ou non en parallèle. J'ai pu écrire une méthode de test qui était et une version qui ne l'était pas, mais dans les deux cas, le comportement est dicté par la méthode elle-même, et non par le délégué qui crée la tâche. Désolé pour la confusion. Cependant, siDoSomethingAsyc
revientTask<Foo>
, alors leawait
dans le délégué n'est pas absolument nécessaire ... Je pense que c'était le point principal que j'allais essayer de faire.Réponses:
LINQ ne fonctionne pas parfaitement avec le
async
code, 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 assez sympa.
WhenAll
renvoie un tableau, donc je pense que votre méthode peut renvoyer les résultats directement:return await Task.WhenAll(tasks);
la source
var tasks = foos.Select(foo => DoSomethingAsync(foo)).ToList();
var tasks = foos.Select(DoSomethingAsync).ToList();
Select
. Mais la plupart n'aiment pasWhere
.async
pour réduire les threads; s'il est lié au processeur et déjà sur un thread d'arrière-plan, alorsasync
ne fournirait aucun avantage.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<T[]> WhenAll<T>(this IEnumerable<Task<T>> source) { return Task.WhenAll(source); } } }
la source
ToArrayAsync
Un problème avec Task.WhenAll est qu'il créerait un parallélisme. Dans la plupart des cas, cela peut être encore mieux, mais parfois vous voulez l'éviter. Par exemple, lire des données par lots à partir de la base de données et envoyer des données à un service Web distant. Vous ne voulez pas charger tous les lots dans la mémoire, mais appuyez sur la base de données une fois que le lot précédent a été traité. Donc, vous devez briser l'asynchronisme. 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 convertissant en synchronisation. DB ne serait touché paresseusement qu'une fois que batchSize des événements ont été traités.
la source
Utilisez
Task.WaitAll
ouTask.WhenAll
selon ce qui est approprié.la source
Task.WaitAll
est bloquant, n'est pas attendu et ne fonctionnera pas avec unTask<T>
.WhenAll
?Task.WhenAll devrait faire l'affaire ici.
la source