web-dev-qa-db-fra.com

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

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 doivent 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<Foo>();
foreach (var task in tasks)
    list.Add(await task);
return list;

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

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

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

79
Matt Johnson-Pint

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 toutes 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 gentil. WhenAll renvoie un tableau, je pense donc que votre méthode peut renvoyer les résultats directement:

return await Task.WhenAll(tasks);
122
Stephen Cleary

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);
        }
    }
}
7
Clement

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 en 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 appuyer sur la base de données 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. La base de données ne serait touchée que lorsque le batchSize des événements a été traité.

3
Boris Lipschitz

Utilisez Task.WaitAll Ou Task.WhenAll Selon le choix.

1
L.B

Task.WhenAll devrait faire l'affaire ici.

0
Ameen