web-dev-qa-db-ja.com

IOバウンドオペレーションの並列実行

TPLとタスクライブラリのドキュメントをすべて読んだことがあります。しかし、それでも次のケースを明確に理解することができず、今すぐ実装する必要があります。

私は自分の状況を単純化します。長さ1000の_IEnumerable<Uri>_があります。HttpClientを使用してリクエストする必要があります。

2つの質問があります。

  1. あまり計算されませんがあり、Httpリクエストを待っているだけです。この場合でもParallel.Foreach()を使用できますか?
  2. 代わりにTaskを使用する場合、それらを大量に作成するためのベストプラクティスは何ですか? Task.Factory.StartNew()を使用して、それらのタスクをリストに追加し、すべてを待つとします。作成できる最大タスク数と最大HttpClientを制御する機能(TPLパーティショナーなど)はありますか?

SOについても同様の質問がいくつかありますが、maximumsについては誰も言及していません。要件は、最大のHttpClientで最大のタスクを使用することです。

前もって感謝します。

16
ozgur

この場合でもParallel.Foreachを使用できますか?

これは実際には適切ではありません。 Parallel.Foreachは、CPUを集中的に使用する作業に適しています。また、非同期操作もサポートしていません。

代わりにTaskを使用する場合、それらを大量に作成するためのベストプラクティスは何ですか?

代わりにTPLDataflowブロックを使用してください。スレッドが利用可能になるのを待ってそこに座っている大量のタスクを作成することはありません。タスクの最大数を構成し、タスクを待機しているバッファーにあるすべてのアイテムにそれらを再利用できます。例えば:

var block = new ActionBlock<Uri>(
    uri => SendRequestAsync(uri),
    new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 50 });

foreach (var uri in uris)
{
    block.Post(uri);
}

block.Complete();
await block.Completion;
22
i3arnon

tPLデータフローに関するi3arnonの答えは良いです。データフローは、CPUとI/Oバウンドのコードが混在している場合に特に役立ちます。 ParallelはCPUバウンドコード用に設計されているという彼の感情を反映します。これはI/Oベースのコードに最適なソリューションではなく、特に非同期コードには適していません。

ほとんどの場合(I/Oコード)でうまく機能し、外部ライブラリを必要としない代替ソリューションが必要な場合、探しているメソッドはTask.WhenAllです。

var tasks = uris.Select(uri => SendRequestAsync(uri)).ToArray();
await Task.WhenAll(tasks);

これは最も簡単な解決策ですが、すべての要求を同時に開始するという欠点があります。特に、すべてのリクエストが同じサービス(またはサービスの小さなセット)に送信される場合、これによりタイムアウトが発生する可能性があります。これを解決するには、ある種のスロットルを使用する必要があります...

作成できる最大タスク数と最大HttpClientを制御する機能(TPLパーティショナーなど)はありますか?

TPLDataflowにはそのNiceMaxDegreeOfParallelismがあり、一度に多くのことを開始するだけです。別の組み込みのSemaphoreSlimを使用して、通常の非同期コードを調整することもできます。

private readonly SemaphoreSlim _sem = new SemaphoreSlim(50);
private async Task SendRequestAsync(Uri uri)
{
  await _sem.WaitAsync();
  try
  {
    ...
  }
  finally
  {
    _sem.Release();
  }
}

代わりにタスクを使用する場合、それらを大量に作成するためのベストプラクティスは何ですか? Task.Factory.StartNew()を使用して、それらのタスクをリストに追加し、すべてを待つとします。

実際にはStartNewを使用したくありません。適切なユースケース(動的タスクベースの並列処理)は1つしかなく、これは非常にまれです。作業をバックグラウンドスレッドにプッシュする必要がある場合、最新のコードではTask.Runを使用する必要があります。しかし、そもそもそれは必要ないので、ここではStartNewTask.Runも適切ではありません。

SOについても同様の質問がいくつかありますが、最大値については誰も言及していません。要件は、最大のHttpClientで最大のタスクを使用することです。

最大値は、非同期コードが実際に扱いにくい場所です。 CPUバウンド(パラレル)コードを使用すると、解決策は明らかです。コアと同じ数のスレッドを使用します。 (まあ、少なくともあなたはそこで開始して必要に応じて調整することができます)。非同期コードでは、解決策はそれほど明白ではありません。それは多くの要因に依存します-あなたが持っているメモリの量、リモートサーバーがどのように応答するか(レート制限、タイムアウトなど)など。

ここに簡単な解決策はありません。特定のアプリケーションが高レベルの同時実行性をどのように処理するかをテストしてから、より低い数値に調整する必要があります。


さまざまなテクノロジー(並列処理、非同期、TPLデータフロー、およびRx)が適切な場合を説明しようとする 講演のスライド がいくつかあります。レシピ付きの記述をもっと好むなら、並行性について 私の本 の恩恵を受けるかもしれないと思います。

27
Stephen Cleary