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.

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

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

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)

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

At a glance

Purpose
Applications on the .NET platform
File extension
.cs
Runs in
.NET runtime
Usually used with
.NET SDK and libraries

Specifications & further reading

Related C# documentation

async and await
The async and await keywords let you write asynchronous code that reads almost like ordinary sequential code. Marking a method async allows it to use await, which pauses execution at that point until the awaited operation completes, without blocking the calling thread - freeing it up to do other work in the meantime. This is essential for keeping applications responsive during I/O-bound work like network calls, file access, or database queries.
Task & Task<T>
Task represents an asynchronous operation that may still be running, and Task<T> represents one that will eventually produce a value of type T. Tasks are the building block that async/await is built on top of - an async method actually returns a Task (or Task<T>) that the caller can await, check the status of, or attach continuations to.
CancellationToken
CancellationToken provides a standard, cooperative way to signal that an asynchronous operation should stop before it completes naturally. A CancellationTokenSource creates the token and controls when cancellation is requested; the token itself is passed into async methods, which check it periodically (or pass it to another cancellable operation) and can throw an OperationCanceledException to unwind cleanly.
Async Pitfalls: void vs Task, ConfigureAwait
A few recurring mistakes trip up many C# developers new to async: using async void instead of async Task (which makes exceptions impossible to catch normally), and blocking on async code with .Result or .Wait() (which can cause a deadlock in contexts with a synchronization context, like older ASP.NET or WPF apps). Understanding these pitfalls up front avoids some of the most common async-related bugs.