アプローチはうまくいくようですが、あなたが示した特定のコードにはいくつかの問題があります。
しかし、私がそれに到達する前に、タスクの並列処理が進むべき道であるという提案がコメントにありました。それは見当違いだと思います。多くの作業を並行して実行したい場合、必然的に多くのスレッドが必要になるという一般的な誤解があります。これは、作業がコンピューティング バウンドである場合にのみ当てはまります。しかし、実行している作業は IO バウンドになります。このコードは、応答を待つ時間の大部分を費やします。それはほとんど計算をしません。したがって、実際には、1 つのスレッドしか使用していなかったとしても、最初の目標である 1 秒あたり 20 リクエストは、1 つの CPU コアに負担をかけるほどのワークロードには見えません。
つまり、1 つのスレッドで非常に高いレベルの同時 IO を処理できます。コードの並列実行が必要な場合にのみ複数のスレッドが必要ですが、この特定のジョブでは CPU の作業がほとんどないため、ここではそうではないようです。
(この誤解はawait
、何年も前から存在していますasync
。実際、それは TPL よりも前から存在しています。少数のスレッドで数千の同時要求を処理できます. Windows ネットワーク IO は基本的に同じように機能するため、基本的な原則は今日でも適用されます.)
ここで複数のスレッドを使用することに特に問題があるというわけではありませんが、それが少し気を散らすものであることを指摘しているだけです。
とにかく、コードに戻ります。この行には問題があります:
Task.Factory.StartNew( asyncWebRequestAndConcurrentCollectionUpdater, args);
すべてのコードを提供していただいたわけではありませんが、どのようにコンパイルできるかわかりません。StartNew
2 つの引数を受け入れるのオーバーロードでは、最初の引数が an Action
、 an Action<object>
、 a Func<TResult>
、または aのいずれかである必要がありFunc<object,TResult>
ます。つまり、引数を取らないか、型の単一の引数を受け入れるobject
(値を返す場合と返さない場合がある) メソッドである必要があります。あなたの「asyncWebRequestAndConcurrentCollectionUpdater」は type の引数を取りますTestArgs
。
しかし、コンパイルできないという事実は、主な問題ではありません。それは簡単に修正できます。(たとえば、 に変更しますTask.Factory.StartNew(() => asyncWebRequestAndConcurrentCollectionUpdater(args));
) 本当の問題は、あなたがしていることは少し奇妙です: を使用Task.StartNew
して、既に . を返すメソッドを呼び出していますTask
。
Task.StartNew
は、同期メソッド (つまり、 a を返さないTask
メソッド) を受け取り、それをノンブロッキングで実行する便利な方法です。(スレッドプールで実行されます。)しかし、すでに を返すメソッドがあるTask
場合は、実際に を使用する必要はありませんでしたTask.StartNew
。何が返されるかを見ると、奇妙なことがより明らかになりTask.StartNew
ます (コンパイルエラーを修正した後):
Task<Task> t = Task.Factory.StartNew(
() => asyncWebRequestAndConcurrentCollectionUpdater(args));
それTask<Task>
は何が起こっているかを明らかにします。非非同期メソッドを非同期にするために通常使用されるメカニズムを使用して、既に非同期になっているメソッドをラップすることにしました。これで、Task
を生成する が得られましたTask
。
これの少し驚くべき結果の 1 つは、 によって返されたタスクがStartNew
完了するのを待っていた場合、基になる作業が必ずしも完了していないということです。
t.Wait(); // doesn't wait for asyncWebRequestAndConcurrentCollectionUpdater to finish!
実際に行うことは、asyncWebRequestAndConcurrentCollectionUpdater
が返されるのを待つことだけですTask
。そして、asyncWebRequestAndConcurrentCollectionUpdater
すでに非同期メソッドであるため、多かれ少なかれすぐにタスクを返します。(具体的には、await
すぐには完了しないタスクを実行した瞬間にタスクを返します。)
開始した作業が完了するまで待ちたい場合は、次のようにする必要があります。
t.Result.Wait();
または、潜在的により効率的に、これ:
t.Unwrap().Wait();
Task
つまり、非同期メソッドが返した を取得してから、それを待ちます。これは、次のはるかに単純なコードとあまり変わらないかもしれません。
Task t = asyncWebRequestAndConcurrentCollectionUpdater("foo");
... maybe queue up some other tasks ...
t.Wait();
`Task.Factory.StartNew' を導入しても、何も役に立たなかったかもしれません。
私が「可能性がある」と言ったのは、重要な条件があるからです。それは、作業を開始する状況によって異なります。C# は、デフォルトで、async
メソッドが の後に継続する場合に、が最初に実行さawait
れたのと同じコンテキストで継続することを保証しようとするコードを生成します。await
たとえば、WPF アプリを使用await
していて UI スレッドを使用している場合、コードが続行されると、UI スレッドで実行するように調整されます。(これは で無効にできますConfigureAwait
。)
したがって、コンテキストが本質的にシリアル化されている状況にある場合 (GUI アプリの場合のようにシングルスレッドであるため、または特定の ASP のコンテキストなど、レンタル モデルに似たものを使用するため) .NET リクエスト) を介して非同期タスクを開始するTask.Factory.StartNew
と、元のコンテキストをエスケープできるため、実際に役立つ場合があります。ただし、あなたの人生をより困難にしただけです。タスクを完了まで追跡することは、やや複雑です。ConfigureAwait
また、メソッド内で を使用するだけで、同じ効果を得ることができたかもしれませんasync
。
とにかく問題ではないかもしれません - 1 秒間に 20 リクエストしか処理しようとしていない場合、それを行うために必要な最小限の CPU 負荷は、おそらく 1 つのスレッドで完全に適切に処理できることを意味します。(また、これがコンソール アプリの場合、スレッド プールを使用する既定のコンテキストが有効になるため、どのような場合でもタスクをマルチスレッドで実行できます。)
しかし、あなたの質問に戻ると、単一のものを持つことは私にとって完全に合理的ですasync
キューから URL を選択し、要求を作成し、応答を調べ、必要に応じて不正な URL コレクションにエントリを追加するメソッド。また、タイマーから物事を開始することも合理的です。これにより、遅い応答で行き詰まることなく、接続が試行される速度が抑制されます (たとえば、大量の要求がオフラインのサーバーと通信しようとする場合)。何万もの URL が連続して応答しないサーバーを指しているという病理学的なケースに遭遇した場合は、処理中のリクエストの最大数に上限を設ける必要があるかもしれません。(関連する注意事項として、使用している HTTP API に関係なく、クライアントごとの接続制限に達しないことを確認する必要があります。これにより、有効なスループットが抑制される可能性があります。)
なんらかの完了処理を追加する必要があります。非同期操作を開始するだけで、結果を処理するために何もしないのは悪い習慣です。行き場のない例外が発生する可能性があるからです。(.NET 4.0 では、これらはプロセスを終了するために使用されていましたが、.NET 4.5 の時点では、非同期操作からの未処理の例外はデフォルトで単純に無視されます! Task.Factory.StartNew
)ラッピングの余分な層になってしまったのでmyTask.Unwrap().ContinueWith(...)
、正しく処理するために何かをする必要があります.