Parallèle.ForEach et async-wait

j'ai eu une telle méthode:

public async Task<MyResult> GetResult()
{
    MyResult result = new MyResult();

    foreach(var method in Methods)
    {
        string json = await Process(method);

        result.Prop1 = PopulateProp1(json);
        result.Prop2 = PopulateProp2(json);

    }

    return result;
}

Alors j'ai décidé d'utiliser Parallel.ForEach:

public async Task<MyResult> GetResult()
{
    MyResult result = new MyResult();

    Parallel.ForEach(Methods, async method =>
    {
        string json = await Process(method);    

        result.Prop1 = PopulateProp1(json);
        result.Prop2 = PopulateProp2(json);
    });

    return result;
}

Mais maintenant j'ai une erreur:

un module ou un manipulateur asynchrone terminé alors qu'une opération asynchrone était encore en attente.

35
demandé sur abatishchev 2014-04-17 19:35:45

3 réponses

async ne fonctionne pas bien avec ForEach. En particulier, votre async lambda est converti en async void méthode. Il y a un certain nombre de raisons d'éviter async void (comme je le décris dans un article de MSDN); l'un d'eux est que vous ne pouvez pas facilement détecter quand le async lambda a terminé. ASP.NET voir votre déclaration de code sans remplir le async void méthode et (à juste titre) lancer une exception.

Ce que vous voulez probablement faire est de traiter les données simultanément en parallèle. Le code parallèle ne devrait presque jamais être utilisé sur ASP.NET. Voici à quoi ressemblerait le code avec un traitement simultané asynchrone:

public async Task<MyResult> GetResult()
{
  MyResult result = new MyResult();

  var tasks = Methods.Select(method => ProcessAsync(method)).ToArray();
  string[] json = await Task.WhenAll(tasks);

  result.Prop1 = PopulateProp1(json[0]);
  ...

  return result;
}
58
répondu Stephen Cleary 2014-04-17 17:34:40

Ahh, d'accord. Je pense que je sais ce qui se passe maintenant. async method => un " async void "qui est" fire and forget " (non recommandé pour autre chose que les gestionnaires d'événements). Cela signifie que l'appelant ne peut pas savoir quand il est terminé... Donc, GetResult retourne alors que l'opération est toujours en cours d'exécution. Bien que les détails techniques de ma première réponse soient incorrects, le résultat est le même ici: que GetResult est de retour alors que les opérations ont commencé par ForEach sont toujours en cours d'exécution. La seule chose que vous pourriez vraiment n'est pas awaitProcess (de sorte que le lambda n'est plus async) et d'attendre Process pour compléter chaque itération. Mais, qui va utiliser au moins un filetage de piscine pour faire cela et donc stressez légèrement la piscine--probablement faire usage de ForEach inutile. Je n'utiliserais simplement pas le parallèle.ForEach...

5
répondu Peter Ritchie 2015-03-16 01:00:01

alternativement, avec le Asyncenumerator NuGet Package vous pouvez faire ceci:

using System.Collections.Async;

public async Task<MyResult> GetResult()
{
    MyResult result = new MyResult();

    await Methods.ParallelForEachAsync(async method =>
    {
        string json = await Process(method);    

        result.Prop1 = PopulateProp1(json);
        result.Prop2 = PopulateProp2(json);
    }, maxDegreeOfParallelism: 10);

    return result;
}

ParallelForEachAsync est une méthode d'extension.

4
répondu Serge Semenov 2016-08-26 21:06:13