219 votes

Comment utiliser Async avec ForEach ?

Est-il possible d'utiliser Async quand on utilise ForEach ? Voici le code que j'essaie d'utiliser :

using (DataContext db = new DataLayer.DataContext())
{
    db.Groups.ToList().ForEach(i => async {
        await GetAdminsFromGroup(i.Gid);
    });
}

Je reçois l'erreur :

Le nom 'Async' n'existe pas dans le contexte actuel.

La méthode dans laquelle se trouve l'instruction using est définie comme asynchrone.

298voto

Stephen Cleary Points 91731

List<T>.ForEach ne joue pas particulièrement bien avec async (LINQ-to-objects non plus, pour les mêmes raisons).

Dans ce cas, je recommande projection de chaque élément dans une opération asynchrone, et vous pouvez ensuite attendre (de manière asynchrone) qu'ils soient tous terminés.

using (DataContext db = new DataLayer.DataContext())
{
    var tasks = db.Groups.ToList().Select(i => GetAdminsFromGroupAsync(i.Gid));
    var results = await Task.WhenAll(tasks);
}

Les avantages de cette approche par rapport à l'octroi d'un async délégué à ForEach sont :

  1. La gestion des erreurs est plus appropriée. Les exceptions de async void ne peut être attrapé avec catch cette approche propage les exceptions au niveau de la await Task.WhenAll ce qui permet un traitement naturel des exceptions.
  2. Vous savez que les tâches sont terminées à la fin de cette méthode, puisqu'elle fait un await Task.WhenAll . Si vous utilisez async void vous ne pouvez pas facilement savoir quand les opérations sont terminées.
  3. Cette approche dispose d'une syntaxe naturelle pour récupérer les résultats. GetAdminsFromGroupAsync On dirait qu'il s'agit d'une opération qui produit un résultat (les admins), et un tel code est plus naturel si de telles opérations peuvent retourner leurs résultats plutôt que de définir une valeur comme un effet secondaire.

94voto

JD Courtoy Points 1019

Cette petite méthode d'extension devrait vous permettre d'obtenir une itération async à l'abri des exceptions :

public static async Task ForEachAsync<T>(this List<T> list, Func<T, Task> func)
{
    foreach (var value in list)
    {
        await func(value);
    }
}

Puisque nous changeons le type de retour de la lambda de void à Task les exceptions se propageront correctement vers le haut. Cela vous permettra d'écrire quelque chose comme ceci en pratique :

await db.Groups.ToList().ForEachAsync(async i => {
    await GetAdminsFromGroup(i.Gid);
});

65voto

Andrei Krasutski Points 1465

En commençant par C# 8.0 vous pouvez créer et consommer des flux de manière asynchrone.

    private async void button1_Click(object sender, EventArgs e)
    {
        IAsyncEnumerable<int> enumerable = GenerateSequence();

        await foreach (var i in enumerable)
        {
            Debug.WriteLine(i);
        }
    }

    public static async IAsyncEnumerable<int> GenerateSequence()
    {
        for (int i = 0; i < 20; i++)
        {
            await Task.Delay(100);
            yield return i;
        }
    }

Plus de

43voto

ckuhn203 Points 1236

La réponse simple est d'utiliser le foreach au lieu du mot-clé ForEach() méthode de List() .

using (DataContext db = new DataLayer.DataContext())
{
    foreach(var i in db.Groups)
    {
        await GetAdminsFromGroup(i.Gid);
    }
}

14voto

Diran Ogunlana Points 444

Voici une version réelle de la variante foreach asynchrone ci-dessus avec un traitement séquentiel :

public static async Task ForEachAsync<T>(this List<T> enumerable, Action<T> action)
{
    foreach (var item in enumerable)
        await Task.Run(() => { action(item); }).ConfigureAwait(false);
}

Voici l'implémentation :

public async void SequentialAsync()
{
    var list = new List<Action>();

    Action action1 = () => {
        //do stuff 1
    };

    Action action2 = () => {
        //do stuff 2
    };

    list.Add(action1);
    list.Add(action2);

    await list.ForEachAsync();
}

Quelle est la principale différence ? .ConfigureAwait(false); qui conserve le contexte du thread principal pendant le traitement séquentiel asynchrone de chaque tâche.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X