Codectionary / Developer documentation / C#

Task.WhenAll & Task.WhenAny

Task.WhenAll() lets you run multiple independent asynchronous operations concurrently and await all of them together, completing once every task finishes - much faster than awaiting them one at a time. Task.WhenAny() completes as soon as the first of several tasks finishes, useful for timeout patterns or taking whichever result arrives first.

Read the syntax

Sequential awaits wait for one operation before starting the next. WhenAll awaits a set of already-started tasks and returns results when all complete; result order matches the task arguments.

WhenAny completes when a task completes, but other tasks keep running unless separately cancelled. Completion order can vary with scheduling and real network conditions.

Syntax

await Task.WhenAll(task1, task2, task3);

Examples

Running Tasks Sequentially (Slower)

Awaiting each task one at a time adds up their durations.

async Task<int> DownloadAsync(string name, int delayMs)
{
    await Task.Delay(delayMs);
    Console.WriteLine($"{name} done");
    return delayMs;
}

// Sequential: total time is roughly the SUM of all delays
int a = await DownloadAsync("File A", 300);
int b = await DownloadAsync("File B", 300);
int c = await DownloadAsync("File C", 300);
// Takes about 900ms total
  1. Awaiting each task one at a time adds up their durations.
  2. Sequential awaits wait for one operation before starting the next. WhenAll awaits a set of already-started tasks and returns results when all complete; result order matches the task arguments.
  3. Each DownloadAsync starts only after the preceding await finishes. The three completion lines appear in A, B, C order and the delays add to roughly 900 ms.

Task.WhenAll: Running Concurrently

Starting all tasks first, then awaiting them together - total time is roughly the longest single task.

async Task<int> DownloadAsync(string name, int delayMs)
{
    await Task.Delay(delayMs);
    Console.WriteLine($"{name} done");
    return delayMs;
}

Task<int> taskA = DownloadAsync("File A", 300);
Task<int> taskB = DownloadAsync("File B", 300);
Task<int> taskC = DownloadAsync("File C", 300);

int[] results = await Task.WhenAll(taskA, taskB, taskC);
Console.WriteLine($"Total: {results.Sum()}ms of work, but ran concurrently");
// Takes about 300ms total, not 900ms
  1. Starting all tasks first, then awaiting them together - total time is roughly the longest single task.
  2. Sequential awaits wait for one operation before starting the next. WhenAll awaits a set of already-started tasks and returns results when all complete; result order matches the task arguments.
  3. All three tasks begin before WhenAll is awaited. Their combined result is 900 ms of simulated work, although elapsed time is roughly one 300 ms delay plus overhead.

Task.WhenAny: First to Finish

Completing as soon as any one of several tasks finishes - useful for timeouts or racing multiple sources.

async Task<string> FetchFromServerAsync(string server, int delayMs)
{
    await Task.Delay(delayMs);
    return $"Response from {server}";
}

Task<string> primary = FetchFromServerAsync("Primary", 500);
Task<string> backup = FetchFromServerAsync("Backup", 200);

Task<string> firstDone = await Task.WhenAny(primary, backup);
Console.WriteLine(await firstDone);  // Response from Backup (it was faster)
  1. Completing as soon as any one of several tasks finishes - useful for timeouts or racing multiple sources.
  2. Sequential awaits wait for one operation before starting the next. WhenAll awaits a set of already-started tasks and returns results when all complete; result order matches the task arguments.
  3. Backup has the shorter simulated delay, so its task normally completes first and prints Response from Backup. The primary task is not automatically cancelled.

Best practices

  • Use Task.WhenAll() whenever you have several independent async operations that do not depend on each other, for a significant speed-up over awaiting them sequentially
  • Start all tasks first (without awaiting each one individually), then pass them all to Task.WhenAll() - awaiting immediately after starting each one defeats the concurrency benefit
  • Use Task.WhenAny() for timeout patterns, by racing your real operation against a Task.Delay() representing the timeout
  • Remember Task.WhenAll() propagates exceptions from all failed tasks (via an AggregateException when awaited via .Result, or the first exception when awaited directly) - handle this appropriately

Runtime & versions

.NET runtime. Feature availability depends on the language and runtime version used by your project; consult the official reference below.

C# language reference

Specifications & further reading