C#のTask.WhenAll()とTask.WaitAll()の違いとは?スレッドのブロック挙動を解説
C#の非同期プログラミングでは、複数のタスクの完了を待ち受けるために Task.WaitAll() と Task.WhenAll() の2つのメソッドがよく使われます。名前はよく似ていますが、両者にはスレッドの扱いにおいて重要な違いがあります。
Task.WaitAllとTask.WhenAllの基本的な違い
Task.WaitAll は、引数に渡されたすべてのタスクの実行が完了するまで、現在のスレッドをブロックします。呼び出し元の処理は、全タスクが完了するまで先に進みません。
一方、Task.WhenAll は「渡されたすべてのタスクが完了したときに初めて完了状態になる新しいタスク」を生成して返すメソッドです。呼び出し時点ではまだ完了していないタスクオブジェクトが返されますが、処理はブロックされず、プログラムはそのまま実行を続けます。
つまり、Task.WaitAll は同期的に待機するのに対し、Task.WhenAll は非同期に待機するためのタスクを作るだけである点が、最大の違いです。
UIスレッドへの影響で理解する
具体例で考えてみましょう。UIスレッドを使ってアニメーションを表示するようなタスクがあるとします。Task.WaitAll を使用すると、ユーザーインターフェースはブロックされ、関連するすべてのタスクが完了してブロックが解除されるまで、画面は更新されません。
しかし、同じアプリケーションで Task.WhenAll を使用すれば、UIスレッドはブロックされないため、画面は通常どおり更新され続けます。GUIアプリケーションではこの違いがユーザー体験に直結するため、非常に重要なポイントです。
Task.WhenAllのサンプルコード
using System;
using System.Threading.Tasks;
namespace DemoApplication{
public class Program{
static void Main(string[] args){
Task task1 = new Task(()=>{
for (int i = 0; i < 5; i++){
Console.WriteLine("Task 1 - iteration {0}", i);
Task.Delay(1000);
}
Console.WriteLine("Task 1 complete");
});
Task task2 = new Task(()=>{
Console.WriteLine("Task 2 complete");
});
task1.Start();
task2.Start();
Console.WriteLine("Waiting for tasks to complete.");
Task.WhenAll(task1, task2);
Console.WriteLine("Tasks Completed.");
Console.ReadLine();
}
}
}
出力結果
Waiting for tasks to complete. Tasks Completed. Task 1 - iteration 0 Task 2 complete Task 1 - iteration 1 Task 1 - iteration 2 Task 1 - iteration 3 Task 1 - iteration 4 Task 1 complete
上記の出力を見ると、「Tasks Completed.」が他のタスクの完了メッセージよりも先に表示されていることがわかります。これは、Task.WhenAll が実行をブロックしないことを示しています。WhenAllが返すタスクは待機されていないため、Mainメソッドは即座に次の行へ進むのです。
Task.WaitAllのサンプルコード
using System;
using System.Threading.Tasks;
namespace DemoApplication{
public class Program{
static void Main(string[] args){
Task task1 = new Task(()=>{
for (int i = 0; i < 5; i++){
Console.WriteLine("Task 1 - iteration {0}", i);
Task.Delay(1000);
}
Console.WriteLine("Task 1 complete");
});
Task task2 = new Task(()=>{
Console.WriteLine("Task 2 complete");
});
task1.Start();
task2.Start();
Console.WriteLine("Waiting for tasks to complete.");
Task.WaitAll(task1, task2);
Console.WriteLine("Tasks Completed.");
Console.ReadLine();
}
}
}
出力結果
Waiting for tasks to complete. Task 1 - iteration 0 Task 2 complete Task 1 - iteration 1 Task 1 - iteration 2 Task 1 - iteration 3 Task 1 - iteration 4 Task 1 complete Tasks Completed.
今度は「Tasks Completed.」が最後に表示されています。つまり、Task.WaitAll はすべてのタスクが完了するまで呼び出し元の処理をブロックしていることが確認できます。
使い分けのポイント
- Task.WaitAll:現在のスレッドを止めてでも全タスクの完了を確実に待ちたい場合に適しています。ただし、UIスレッドで呼び出すと画面が固まるため注意が必要です。
- Task.WhenAll:async/awaitと組み合わせて
await Task.WhenAll(...)のように使うのが一般的です。スレッドをブロックせずに複数タスクの完了を待てるため、UIアプリやWebアプリケーションに向いています。
なお、サンプルコード内の Task.Delay(1000) はawaitされていないため、実際には遅延として機能しない点に留意してください。実運用のコードでは await Task.Delay(1000) のように記述します。
-
DirectX11とDirectX12の違いを徹底解説!ゲーマーが知っておくべきポイント
DirectXは、ソフトウェアがマルチメディアコンテンツを描画し、グラフィックハードウェアと通信するために使用されるAPI(アプリケーションプログラミングインターフェース)のコレクションです。DirectXの主要な構成要素であるDirect3Dは、ソフトウェアとグラフィックスハードウェア間の通信を担っています。ハードウェアは製品ごとに異なるため、標準化された関数や呼び出しのライブラリを通じてグラフィックカードと通信することで、ゲーム開発を大幅にスピードアップできます。これは「ハードウェア抽象化」と呼ばれ、APIの最も重要な役割となっています。車に例えてみましょう。1台の車の運転ができれば、ほぼ
-
C#の配列とリスト(List)の違いとは?特徴と基本的な使い方を解説
C#における配列(Array)とリスト(List<T>)は、どちらも複数のデータをまとめて扱うためのコレクションですが、その性質には重要な違いがあります。 配列とリストの主な違い 配列は、生成時にサイズが固定される、同じ型の要素を順番に格納するコレクションです。一度作成した後は要素数を変更できません。 一方、Listはジェネリックコレクションであり、要素の追加や削除に応じてサイズが自動的に拡張・縮小します。この柔軟性が最大の特徴です。 項目配列List<T> サイズ固定可変(動的) 要素の追加不可(再作成が必要)Addメソッドで可能 宣言方法型[] 変数名Lis