Task.WhenAll vs WhenAny vs WhenEach vs Parallel.ForEachAsync in C#: choosing the right tool

C# Async & Multithreading November 13, 2026 csharp

Pick the tool by scenario:

csharp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// ======================================================================
// Q: WhenAll, WhenAny, WhenEach, Parallel: which do you pick?
//
// Pick the tool by scenario:
// - 2-3 independent I/O calls: Task.WhenAll.
// - Many items, I/O, with a limit on concurrent calls: Parallel.ForEachAsync
//   (MaxDegreeOfParallelism) or SemaphoreSlim.
// - CPU-bound work over a collection: Parallel.ForEach, PLINQ.
// - Results in the order they complete: Task.WhenEach (.NET 9).
// - The fastest of two, or a timeout: Task.WhenAny, WaitAsync.
// - Producer/consumer within the same process: Channel<T>.
// - Across pods / services: message broker + idempotent consumer (Q30-Q31).
// - Periodic work: BackgroundService + PeriodicTimer.
// ======================================================================

// First wins: query two warehouses, keep the fastest, cancel the other
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
var primary = warehouseA.GetStockAsync(sku, cts.Token);
var backup  = warehouseB.GetStockAsync(sku, cts.Token);
var winner = await Task.WhenAny(primary, backup);
await cts.CancelAsync();             // stops the slower call
return await winner;                 // throws if the faster one failed

// WhenEach: show results as they arrive
await foreach (var done in Task.WhenEach(warehouses.Select(w => w.GetStockAsync(sku, ct))))
    progress.Report(await done);     // already completed