web-dev-qa-db-ja.com

Parallel.ForEachおよびasync-await

私はそのような方法がありました:

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

次に、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;
}

しかし、今私はエラーを持っています:

非同期操作がまだ保留中に、非同期モジュールまたはハンドラーが完了しました。

37
sreginogemoh

asyncForEachではうまく機能しません。特に、asyncラムダはasync voidメソッドに変換されています。多くの async voidを避ける理由 があります(MSDNの記事で説明しています)。それらの1つは、asyncラムダがいつ完了したかを簡単に検出できないことです。 ASP.NETは、async voidメソッドを完了せずにコードが戻ることを確認し、(適切に)例外をスローします。

おそらくやりたいことは、concurrentlyにデータを処理することです。parallel。 ASP.NETで並列コードを使用することはほとんどありません。非同期同時処理を使用した場合のコードは次のとおりです。

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;
}
63
Stephen Cleary

または、 AsyncEnumerator NuGet Package でこれを行うことができます。

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は拡張メソッドです。

6
Serge Semenov

ああ、大丈夫。私は今何が起こっているか知っていると思います。 async method =>「fire and forget」である「async void」(イベントハンドラ以外にはお勧めしません)。これは、呼び出し側が完了したことを知ることができないことを意味します...したがって、GetResultは、操作の実行中に戻ります。最初の答えの技術的な詳細は間違っていますが、結果はここでは同じです。つまり、ForEachによって開始された操作がまだ実行されている間にGetResultが返されます。本当にできるのは、awaitProcessを(ラムダがasyncでなくなるように)しないことと、各反復が完了するまでProcessを待つことだけです。しかし、それは少なくとも1つのスレッドプールスレッドを使用してそれを行うため、ForEachを無意味に使用する可能性があります。私は単にParallel.ForEachを使用しないでしょう...

5
Peter Ritchie