← Back to Articles         
Difference between Task.WhenAll() and Task.WhenAny()

Difference between Task.WhenAll() and Task.WhenAny()

Published on 12 Oct 2026     10 min read C#
Asynchronous Programming (Async/Await)

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

Task.WhenAll()

Task.WhenAny()

Purpose

Wait for all tasks

Wait for first completed task

Completion

All tasks finish

Any one task finishes

Returns

Task or Task<T[]>

Task<Task> or Task<Task<T>>

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

  1. DownloadFileAsync() is called three times.

  2. Each method starts and reaches Task.Delay().

  3. The three asynchronous delays are pending concurrently.

  4. Task.WhenAll() creates a task representing completion of all three operations.

  5. File 3 completes after approximately one second.

  6. File 1 completes after approximately two seconds.

  7. File 2 completes after approximately three seconds.

  8. 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 Exception property.

  • 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

  1. Three asynchronous API operations are started.

  2. API 1 takes approximately three seconds.

  3. API 2 takes approximately one second.

  4. API 3 takes approximately two seconds.

  5. Task.WhenAny() completes when API 2 finishes.

  6. It returns the completed Task<string>.

  7. The second await extracts the result from that task.

  8. The remaining tasks continue running.

  9. 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:

  1. All three service operations are initiated.

  2. The operations make progress concurrently.

  3. Task.WhenAll() waits until all three complete.

  4. 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:

  1. Both API requests are started.

  2. Task.WhenAny() identifies the first completed request.

  3. The result of that request is returned.

  4. 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

Task.WhenAll()

Call multiple independent APIs and combine results

Task.WhenAll()

Process multiple independent files concurrently

Task.WhenAll()

Wait until several background operations finish

Task.WhenAll()

Select the first completed API request

Task.WhenAny()

Implement a custom timeout race

Task.WhenAny()

Process results as tasks finish

Task.WhenAny()

Detect which operation completes first

Task.WhenAny()

10. Important Interview Key Points

  1. Task.WhenAll() waits for all tasks to finish.

  2. Task.WhenAny() waits for the first task to finish.

  3. Neither method automatically starts a new thread.

  4. Both support Task and Task<T>.

  5. Task.WhenAll() with generic tasks returns all results as an array.

  6. Task.WhenAny() returns the first completed task.

  7. Task.WhenAll() retains exceptions from faulted tasks.

  8. Task.WhenAny() does not automatically cancel the remaining tasks.

  9. Neither method guarantees that operations run on separate threads.

  10. 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.

← Back to Articles