Webエンジニア向けプログラミング解説動画をYouTubeで配信中!
▶ チャンネル登録はこちら

【ITニュース解説】Task.WhenEach in .NET: Process Tasks as They Complete

2025年09月08日に「Dev.to」が公開したITニュース「Task.WhenEach in .NET: Process Tasks as They Complete」について初心者にもわかりやすく解説しています。

作成日: 更新日:

ITニュース概要

.NETの非同期処理では、複数のタスクが同時に動く際、従来は全完了を待つか、どれか一つを待つ方法が主流だった。.NET 6+のTask.WhenEachを使えば、タスクが完了したものから順に結果を処理できる。遅いタスクを待つ必要がなく、素早く応答するアプリケーションを開発できる。

ITニュース解説

ソフトウェア開発において、ユーザーインターフェースが固まったり、プログラム全体の応答が遅くなったりするのを防ぐため、時間のかかる処理をバックグラウンドで実行することは非常に重要だ。このような処理を「非同期処理」と呼び、.NETでは「タスク(Task)」という仕組みを使ってこれを実現している。複数のタスクを同時に実行し、それぞれの結果を待つ必要がある場面は非常に多く、その管理方法がアプリケーションの性能や応答性を大きく左右する。

これまで、複数のタスクを扱う主な方法として、「Task.WhenAll」と「Task.WhenAny」の二つが使われてきた。「Task.WhenAll」は、与えられた全てのタスクが完了するまで、次の処理に進まないという特徴がある。例えば、複数のウェブサイトからデータを取得する場合を考えてみよう。この場合、一番遅いウェブサイトからのデータが届くまで、全てのデータの処理を開始できないといった状況が発生する。これでは、一部のタスクが早く終わっていても、最も処理に時間のかかるタスクに全体の進行が引きずられてしまうという問題があった。結果として、ユーザーは全てのデータが揃うまで待たされることになり、アプリケーションの応答性が低下する原因となるのだ。

一方、「Task.WhenAny」は、これとは対照的に、与えられたタスクのうち、どれか一つでも最初に完了すれば、すぐに次の処理に進むことができる。これは、例えば複数のサーバーに同じリクエストを送り、一番早く応答があったサーバーの結果だけを使いたい、といった場合に有効な方法だ。しかし、「Task.WhenAny」では一つのタスクの結果しか扱えず、他のタスクが完了してもその結果は利用できないという制約があった。

このような従来のタスク管理の課題を解決するために、.NET 6以降で「Task.WhenEach」という新しい機能が導入された。これは、複数のタスクを実行する際に、それぞれのタスクが完了した時点ですぐにその結果を処理できるという画期的な仕組みだ。「Task.WhenAll」のように全てのタスクの完了を待つ必要もなければ、「Task.WhenAny」のように一つの結果に限定されることもない。つまり、最も時間のかかるタスクを待たずに、処理が終わったタスクから順に、その結果をリアルタイムに活用できるようになったのだ。

「Task.WhenEach」を使う最大のメリットは、アプリケーションの応答性が大幅に向上することである。例えば、複数のAPIから異なる速度でデータが返ってくるような状況を想像してみよう。「Task.WhenAll」では、最も応答の遅いAPIからのデータが届くまで、全ての処理を待たなければならなかった。しかし、「Task.WhenEach」を使えば、早く応答があったAPIからのデータはすぐに画面に表示したり、次の処理に進めたりできる。これは、部分的な結果が利用可能になった時点で即座にユーザーにフィードバックを提供できるため、ユーザー体験が格段に向上することを意味する。また、リアルタイム性が求められるアプリケーションや、継続的にデータが流れ込んでくるストリーミング処理などにおいて、処理の遅延を大幅に削減できるという利点もある。

具体的なコード例でこの違いを見てみよう。もし三つのタスクがあり、それぞれ3秒、1秒、2秒で完了すると仮定する。

「Task.WhenAll」を使った場合、一番遅い3秒のタスクが完了するまで待機する。そのため、実際には1秒で終わるタスクがあっても、全てのタスクが完了する3秒後までその結果を処理できない。結果の出力順序は、タスクを定義した順序になることが多く、タスクが実際に完了した順序とは異なる場合がある。つまり、「タスク1(3秒)」、「タスク2(1秒)」、「タスク3(2秒)」という順序で定義されていれば、出力も「タスク1」、「タスク2」、「タスク3」となるが、全て3秒後にまとめて処理される。

一方、「Task.WhenEach」を使った場合、「await foreach」という特別な構文と組み合わせて使うことで、タスクが完了した順にその結果を処理できる。先ほどの例では、まず1秒で完了する「タスク2」の結果がすぐに処理され、次に2秒で完了する「タスク3」の結果が処理される。最後に、3秒で完了する「タスク1」の結果が処理されることになる。このように、「Task.WhenEach」はタスクの完了順に結果を処理するため、出力順序も「タスク2」、「タスク3」、「タスク1」というように、タスクの実行速度に応じた順序となる。これにより、アプリケーションは最も早く完了したタスクの結果を即座に利用できるようになるのだ。

「Task.WhenEach」は、現実世界の様々なシナリオで非常に役立つ。例えば、複数の外部APIからデータを取得して一つの画面に表示する「API集約」の場面では、一部のAPIが遅くても、他のAPIから取得できたデータはすぐに表示できる。これにより、ユーザーは待機時間を短縮できる。また、巨大なファイルを分割して処理する際にも、部分的に処理が完了したデータを随時利用していくことで、全体の処理をより効率的に進められる。「Webクローリング」のような、インターネット上の情報を収集するアプリケーションでは、一部のウェブサイトからの情報が取得できたら、すぐにその情報をユーザーに提示したり、データベースに保存したりできる。さらに、動画や音声などの「ストリーミング」データのように、継続的に送られてくるデータを処理する際にも、「Task.WhenEach」は、全てのデータが揃うのを待つのではなく、届いたデータから順に処理していくことで、リアルタイムな処理を実現可能にする。

このように、「Task.WhenEach」は非同期プログラミングに新たな選択肢を提供してくれる。では、これら三つの機能をどのように使い分ければ良いのだろうか。 「Task.WhenAll」は、全てのタスクの結果が揃ってからでないと次の処理に進めない場合、つまり、全てのデータが完全に揃っていることが前提となる場合に適している。 「Task.WhenAny」は、複数の選択肢の中から最も早く完了した一つだけを利用すれば十分な場合に使うと良い。 そして「Task.WhenEach」は、タスクが完了するたびにその結果を逐次的に処理していきたい場合、部分的な結果でもすぐに利用したい場合、あるいは応答性を最大限に高めたい場合に最適な選択肢となる。

「Task.WhenEach」は、.NETの非同期プログラミングにおいて非常に強力なツールであり、アプリケーション開発に大きな変化をもたらす。最も遅い処理に全体が引きずられることなく、完了したタスクから順次処理を進められるため、より応答性が高く、効率的なアプリケーションを構築できる。特に「await foreach」構文と組み合わせることで、非同期ストリームとしてタスクの結果を扱うことが可能になり、リアルタイムなダッシュボードやレスポンシブなAPIを構築する上で非常に有効な手段となるだろう。これまでの「Task.WhenAll」を使っていたシナリオの一部を「Task.WhenEach」に置き換えてみることで、その効果を実感できるはずだ。

関連コンテンツ