Task.WhenAll()과 Task.WaitAll(), 무엇이 다를까?
Task.WaitAll()은 현재 스레드를 차단(block)하여, 인자로 전달된 모든 작업(Task)이 실행을 마칠 때까지 대기합니다. 반면 Task.WhenAll()은 전달된 모든 작업이 완료되었을 때 비로소 완료 상태가 되는 새로운 Task 객체를 생성하는 메서드입니다.
Task.WhenAll()을 호출하면 아직 완료되지 않은 Task 객체가 반환되지만, 이 호출 자체는 프로그램의 흐름을 막지 않고 코드가 계속 실행됩니다. 반대로 Task.WaitAll()은 실제로 현재 스레드를 차단하며, 모든 작업이 끝날 때까지 기다립니다.
쉬운 예로 UI 스레드에서 애니메이션을 표시해야 하는 상황을 들 수 있습니다. Task.WaitAll()을 사용하면 관련된 모든 작업이 완료되어 차단이 해제될 때까지 UI가 갱신되지 않고 멈춘 것처럼 보입니다. 그러나 같은 상황에서 Task.WhenAll()을 사용하면 UI 스레드는 차단되지 않으므로 화면이 평소처럼 정상적으로 갱신됩니다.
참고로 실무에서는 Task.WhenAll()의 반환값을 버리지 않고 await Task.WhenAll(task1, task2);처럼 비동기 컨텍스트에서 함께 사용하는 것이 권장됩니다. 단순히 호출만 하면 완료를 기다리는 동작이 없기 때문입니다.
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
위 실행 결과를 보면 Task.WhenAll()을 사용했을 때 "Tasks Completed." 문구가 다른 작업들이 모두 끝나기 전에 먼저 출력된 것을 확인할 수 있습니다. 이는 Task.WhenAll()이 실행을 차단하지 않는다는 의미입니다.
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.
반면 Task.WaitAll()을 사용한 예제에서는 "Tasks Completed." 문구가 모든 작업이 완료된 후에야 출력되었습니다. 즉, Task.WaitAll()은 모든 작업이 끝날 때까지 현재 스레드의 실행을 차단한다는 것을 알 수 있습니다.
정리
요약하면 Task.WaitAll()은 동기 방식으로 스레드를 차단하며 대기하고, Task.WhenAll()은 차단 없이 완료 시점을 추적할 수 있는 Task를 반환합니다. UI 애플리케이션이나 높은 응답성이 필요한 환경에서는 스레드를 점유하지 않는 Task.WhenAll()을 async/await와 함께 사용하는 것이 바람직합니다.