← Back

Task.WhenEach vs Task.WhenAll vs Serialized await

2026-01-07 19:47 · 👁 10507

#c#

🚀 C# Async Tip — Task.WhenEach vs Task.WhenAll vs Serialized await

These three patterns look similar, but they solve different problems:

  • WhenAllwait for everything, then continue
  • WhenEachprocess results as tasks complete
  • Serialized awaitrun one at a time (or control concurrency)

1️⃣ Task.WhenAll — “I need all results”

Best when you must wait for everything (e.g., build a combined response).

var tasks = ids.Select(id => client.GetAsync(id)).ToArray();

var results = await Task.WhenAll(tasks); // completes when ALL complete
return results;

✅ Fast overall (parallel)
✅ Great for “fan-out / fan-in”
⚠️ If one fails, the returned task faults (you’ll handle exceptions at the await).

Use when: you only care after everything is done.


2️⃣ Task.WhenEach — “I want results as soon as they’re ready”

WhenEach returns an IAsyncEnumerable<Task> that yields tasks as they complete, so you can stream/handle progress naturally.

var tasks = urls.Select(url => http.GetStringAsync(url)).ToArray();

await foreach (var completed in Task.WhenEach(tasks))
{
    try
    {
        var body = await completed;       // observe result (or exception)
        Console.WriteLine(body.Length);   // process immediately
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);    // handle per-task failures
    }
}

✅ Best UX/throughput for “process as you go”
✅ Great for pipelines, progress updates, partial success
⚠️ You still need to await each yielded task to observe exceptions/results

Use when: you benefit from early results (UI updates, streaming, incremental aggregation).


3️⃣ Serialized await — “I need it sequential (or controlled)”

True serialized execution = start the next operation only after the previous finishes:

foreach (var url in urls)
{
    var body = await http.GetStringAsync(url); // one-at-a-time
    Process(body);
}

✅ Predictable load (good for rate limits)
✅ Simplest debugging
❌ Slowest wall-clock time

Use when: the dependency is sequential, or you must respect strict rate limits / ordering.


⚠️ Important gotcha: “Serialized await” can be fake

This starts everything in parallel, then awaits one by one (still parallel execution!):

var tasks = urls.Select(http.GetStringAsync).ToList();

foreach (var t in tasks)
{
    var body = await t; // tasks already running
    Process(body);
}

If you want true control, you need sequential awaits or a concurrency limiter.


🧠 Quick rule of thumb

  • Need all results at the end?Task.WhenAll
  • Want to process as soon as each finishes?Task.WhenEach
  • Need strict ordering / rate limiting / less load? → Serialized await

🎯 Final takeaway

This isn’t a “which is faster” debate. It’s a workflow decision:

  • WhenAll optimizes for completion
  • WhenEach optimizes for throughput + responsiveness
  • Serialized await optimizes for control

#dotnet #csharp #async #performance #softwareengineering

Rejoining the server...

Rejoin failed... trying again in seconds.

Failed to rejoin.
Please retry or reload the page.

The session has been paused by the server.

Failed to resume the session.
Please reload the page.