C# Asynchronous Programming: Difference Between Task.WhenAll() and Task.WhenAny()
1. What are Task.WhenAll() and Task.WhenAny()?
Both Task.WhenAll() and Task.WhenAny() are methods in the System.Threading.Tasks.Task class.
They are used to coordinate multiple asynchronous operations.
The main difference is:
-
Task.WhenAll()– Waits asynchronously until all tasks complete. -
Task.WhenAny()– Waits asynchronously until at least one task completes.
Both methods are useful when multiple independent operations can execute concurrently.
2. Comparison Table
|
Feature |
|
|
|---|---|---|
|
Purpose |
Wait for all tasks |
Wait for first completed task |
|
Completion |
All tasks finish |
Any one task finishes |
|
Returns |
|
|
|
Other tasks |
All must finish |
Others continue running |
|
Execution |
Concurrent task coordination |
Concurrent task coordination |
|
Exception handling |
Faults if any task faults |
Returns first completed task, even if faulted |
|
Common use |
Multiple API calls |
Fastest API response |
|
Performance |
Depends on slowest task |
Depends on first completed task |
Important: Neither method starts tasks by itself. They coordinate tasks that have already been created or started.
3. How Does Task.WhenAll() Work?
Task.WhenAll() creates a task that completes when all supplied tasks have completed.
Example Program
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
Console.WriteLine("Application Started");
Task task1 = DownloadFileAsync("File 1", 2000);
Task task2 = DownloadFileAsync("File 2", 3000);
Task task3 = DownloadFileAsync("File 3", 1000);
await Task.WhenAll(task1, task2, task3);
Console.WriteLine("All Files Downloaded");
}
static async Task DownloadFileAsync(
string fileName, int delay)
{
Console.WriteLine($"{fileName} Started");
await Task.Delay(delay);
Console.WriteLine($"{fileName} Completed");
}
}
Output
Application Started
File 1 Started
File 2 Started
File 3 Started
File 3 Completed
File 1 Completed
File 2 Completed
All Files Downloaded
Step-by-Step Explanation
-
DownloadFileAsync()is called three times. -
Each method starts and reaches
Task.Delay(). -
The three asynchronous delays are pending concurrently.
-
Task.WhenAll()creates a task representing completion of all three operations. -
File 3 completes after approximately one second.
-
File 1 completes after approximately two seconds.
-
File 2 completes after approximately three seconds.
-
Only after all three complete does execution continue after
await Task.WhenAll().
The download methods simulate operations using delays; they do not actually download files.
Execution Flow
Main()
|
v
Start Three Tasks
|
+---------+---------+
| | |
v v v
Task 1 Task 2 Task 3
2 sec 3 sec 1 sec
| | |
v | |
Completed | |
| v
| Completed
|
v
Completed
|
+---------+---------+
|
v
Task.WhenAll()
|
v
All Tasks Completed
|
v
Main() Continues
Total Execution Time
Illustrative task timeline
Sequential execution:
await DownloadFileAsync("File 1", 2000);
await DownloadFileAsync("File 2", 3000);
await DownloadFileAsync("File 3", 1000);
Total time: approximately 6 seconds.
Concurrent execution:
Task task1 = DownloadFileAsync("File 1", 2000);
Task task2 = DownloadFileAsync("File 2", 3000);
Task task3 = DownloadFileAsync("File 3", 1000);
await Task.WhenAll(task1, task2, task3);
Total time: approximately 3 seconds.
This is because the asynchronous waits overlap.
Key Points
-
Task.WhenAll()waits for all supplied tasks to complete. -
Tasks can make progress concurrently.
-
It is useful for executing multiple independent API calls or database queries.
-
If any task fails, the combined task becomes faulted after all tasks finish.
-
If multiple tasks fail, their exceptions are available through the combined task's
Exceptionproperty. -
Task.WhenAll()does not create additional threads automatically.
4. How Does Task.WhenAny() Work?
Task.WhenAny() creates a task that completes when the first supplied task finishes.
The remaining tasks continue executing unless they are explicitly cancelled or otherwise stopped.
Example Program
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
Console.WriteLine("Application Started");
Task<string> task1 = GetDataAsync("API 1", 3000);
Task<string> task2 = GetDataAsync("API 2", 1000);
Task<string> task3 = GetDataAsync("API 3", 2000);
Task<string> completedTask =
await Task.WhenAny(task1, task2, task3);
string result = await completedTask;
Console.WriteLine($"First Response: {result}");
await Task.WhenAll(task1, task2, task3);
}
static async Task<string> GetDataAsync(
string apiName, int delay)
{
Console.WriteLine($"{apiName} Started");
await Task.Delay(delay);
Console.WriteLine($"{apiName} Completed");
return $"{apiName} Response";
}
}
Output
Application Started
API 1 Started
API 2 Started
API 3 Started
API 2 Completed
First Response: API 2 Response
API 3 Completed
API 1 Completed
Step-by-Step Explanation
-
Three asynchronous API operations are started.
-
API 1 takes approximately three seconds.
-
API 2 takes approximately one second.
-
API 3 takes approximately two seconds.
-
Task.WhenAny()completes when API 2 finishes. -
It returns the completed
Task<string>. -
The second
awaitextracts the result from that task. -
The remaining tasks continue running.
-
The final
Task.WhenAll()in this example waits for the remaining operations before the program exits.
Execution Flow
Main()
|
v
Start Three Tasks
|
+---------+---------+
| | |
v v v
Task 1 Task 2 Task 3
3 sec 1 sec 2 sec
| | |
| v |
| Completed |
| | |
| v |
| Task.WhenAny() |
| | |
| v |
| Return Task 2 |
| | |
| v |
| Get Task Result|
| |
v v
Continues Continues
| |
v v
Completed Completed
Total Execution Time
Task.WhenAny() returns after approximately 1 second because API 2 completes first.
The remaining tasks continue running.
The complete sample program takes approximately three seconds because the final Task.WhenAll() also waits for the other tasks.
Key Points
-
Task.WhenAny()waits for the first task to finish. -
It returns the completed task, not directly the task's result.
-
Other tasks continue executing.
-
The first completed task might succeed, fail, or be cancelled.
-
You should await the returned task to retrieve its result or observe its exception.
-
It is useful for timeout handling, redundant service calls, and processing tasks as they complete.
5. Important Difference: Return Types
Task.WhenAll() with results
When multiple Task<T> operations are supplied, Task.WhenAll() returns a Task<T[]>.
Task<int> task1 = Task.FromResult(10);
Task<int> task2 = Task.FromResult(20);
Task<int> task3 = Task.FromResult(30);
int[] results = await Task.WhenAll(
task1, task2, task3);
Console.WriteLine(string.Join(", ", results));
Output:
10, 20, 30
The results are returned in the same order as the input tasks, regardless of their completion order.
Task.WhenAny() with results
Task.WhenAny() returns the first completed task.
Task<int> task1 = GetValueAsync(100, 3000);
Task<int> task2 = GetValueAsync(200, 1000);
Task<int> completedTask =
await Task.WhenAny(task1, task2);
int result = await completedTask;
Console.WriteLine(result);
static async Task<int> GetValueAsync(int value, int delay)
{
await Task.Delay(delay);
return value;
}
Output:
200
The important syntax difference is:
// WhenAll: await returns all results
int[] results = await Task.WhenAll(task1, task2);
// WhenAny: await returns the completed task
Task<int> firstTask = await Task.WhenAny(task1, task2);
// Await again to obtain its result
int firstResult = await firstTask;
6. Exception Handling Differences
This is an important interview topic.
A. Exception Handling in Task.WhenAll()
static async Task Main()
{
Task task1 = ProcessAsync("Task 1", false);
Task task2 = ProcessAsync("Task 2", true);
Task task3 = ProcessAsync("Task 3", false);
try
{
await Task.WhenAll(task1, task2, task3);
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
static async Task ProcessAsync(string name, bool fail)
{
await Task.Delay(1000);
if (fail)
throw new Exception($"{name} Failed");
Console.WriteLine($"{name} Completed");
}
Possible output:
Task 1 Completed
Task 3 Completed
Error: Task 2 Failed
Explanation:
-
All three tasks are started.
-
Task 2 throws an exception.
-
Task 1 and Task 3 still complete.
-
Task.WhenAll()becomes faulted. -
Awaiting it throws an exception.
If multiple tasks fail, the combined task retains their exceptions. An ordinary await typically throws one of them, rather than automatically throwing every exception separately.
B. Exception Handling in Task.WhenAny()
Task<int> task1 = GetValueAsync(10, 2000);
Task<int> task2 = GetValueAsync(20, 1000);
Task<int> completedTask =
await Task.WhenAny(task1, task2);
try
{
int result = await completedTask;
Console.WriteLine(result);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
Explanation:
-
Task.WhenAny()completes when one task finishes. -
It does not throw merely because the winning task failed.
-
Awaiting the winning task propagates its exception if it failed.
-
Other tasks may still fail later, so their completion and exceptions must also be managed.
7. Real-World Scenario: ASP.NET Core Web API
Scenario A: Fetching Multiple Independent API Responses
Suppose your application needs employee, department, and salary details from different services.
public async Task<EmployeeDashboardDto> GetDashboardAsync(int id)
{
Task<EmployeeDto> employeeTask =
_employeeService.GetEmployeeAsync(id);
Task<DepartmentDto> departmentTask =
_departmentService.GetDepartmentAsync(id);
Task<SalaryDto> salaryTask =
_salaryService.GetSalaryAsync(id);
await Task.WhenAll(
employeeTask,
departmentTask,
salaryTask);
return new EmployeeDashboardDto
{
Employee = await employeeTask,
Department = await departmentTask,
Salary = await salaryTask
};
}
Explanation:
-
All three service operations are initiated.
-
The operations make progress concurrently.
-
Task.WhenAll()waits until all three complete. -
Their results are combined into a dashboard response.
This assumes the operations are independent and the services do not concurrently use the same EF Core DbContext instance, which does not support parallel operations.
Scenario B: Selecting the Fastest API Response
Suppose the same information is available from two independent external services.
public async Task<string> GetFastestResponseAsync()
{
Task<string> api1 = _apiService1.GetDataAsync();
Task<string> api2 = _apiService2.GetDataAsync();
Task<string> completedTask =
await Task.WhenAny(api1, api2);
return await completedTask;
}
Explanation:
-
Both API requests are started.
-
Task.WhenAny()identifies the first completed request. -
The result of that request is returned.
-
The other request continues unless explicitly cancelled.
For production applications, cancellation and exception handling are important. The first completed request may be a failure rather than a successful response.
8. Can Task.WhenAll() and Task.WhenAny() Execute Tasks in Parallel?
They can coordinate concurrently running tasks, but neither method itself creates threads or starts execution.
For example:
Task task1 = ProcessAsync();
Task task2 = ProcessAsync();
await Task.WhenAll(task1, task2);
If ProcessAsync() performs asynchronous I/O, both operations can overlap without requiring separate threads for their waiting periods.
If you need CPU-bound operations to run on ThreadPool threads, you can explicitly schedule them using Task.Run().
Task task1 = Task.Run(() => Calculate1());
Task task2 = Task.Run(() => Calculate2());
await Task.WhenAll(task1, task2);
Here, Task.Run() schedules the work. Task.WhenAll() only coordinates completion.
9. When Should You Use Each?
|
Requirement |
Recommended |
|---|---|
|
Fetch employee and department details together |
|
|
Call multiple independent APIs and combine results |
|
|
Process multiple independent files concurrently |
|
|
Wait until several background operations finish |
|
|
Select the first completed API request |
|
|
Implement a custom timeout race |
|
|
Process results as tasks finish |
|
|
Detect which operation completes first |
|
10. Important Interview Key Points
-
Task.WhenAll()waits for all tasks to finish. -
Task.WhenAny()waits for the first task to finish. -
Neither method automatically starts a new thread.
-
Both support
TaskandTask<T>. -
Task.WhenAll()with generic tasks returns all results as an array. -
Task.WhenAny()returns the first completed task. -
Task.WhenAll()retains exceptions from faulted tasks. -
Task.WhenAny()does not automatically cancel the remaining tasks. -
Neither method guarantees that operations run on separate threads.
-
Use
Task.WhenAll()for independent concurrent operations whose results are all required.
11. Interview Summary
Task.WhenAll() is used when we need to wait for all asynchronous operations to complete, such as fetching data from multiple independent services.
Task.WhenAny() is used when we need to identify the first completed asynchronous operation, such as selecting the fastest response or implementing a timeout.
Task.WhenAll()
|
+-- Task 1 Completed
+-- Task 2 Completed
+-- Task 3 Completed
|
v
Continue after ALL tasks finish
Task.WhenAny()
|
+-- Task 1 Running
+-- Task 2 Completed <-- First
+-- Task 3 Running
|
v
Continue after FIRST task finishes
Remember: WhenAll() means all tasks must finish; WhenAny() means one task finishing is enough.