Skip to main content
๐Ÿ“œ WAYPOINT LESSON

Fan-out, Cancellation, and Timeouts

โญโญ intermediateโณ 17 min read๐Ÿ“ Lesson 114 of 265

Task.WhenAll for concurrency, CancellationToken through every layer, and time-boxing with WhenAny + Task.Delay.

Sequential awaits vs WhenAll fan-out

Awaits in sequence run one after another. Start all tasks FIRST, then await them โ€” they overlap:

Task<int> a = ComputeAsync(1);          // starts now
Task<int> b = ComputeAsync(2);          // starts now
int[] both = await Task.WhenAll(a, b);  // total wait = max, not sum

Calling ComputeAsync runs the method synchronously until its first await, then returns the running Task. That's why "start then await" is not the same as "await then start" โ€” a fan-out where the first await comes before the last start is mostly sequential.

WhenAll re-throws the FIRST exception; inspect each task's status if partial results matter.

CancellationToken: the cooperative contract

Cancellation is COOPERATIVE โ€” the token signals, your code checks:

async Task WorkAsync(CancellationToken ct)
{
    for (int i = 0; i < 1000; i++)
    {
        ct.ThrowIfCancellationRequested();     // or ct.IsCancellationRequested
        await StepAsync(i, ct);                // pass the token DOWN
    }
}

Pass the token into every async call; a cancellation that never reaches the innermost operation is not cancellation. Throwing OperationCanceledException (the token's job) is the convention callers expect.

Timeouts: WhenAny + Task.Delay, or WaitAsync

string body = await FetchAsync(ct).WaitAsync(TimeSpan.FromSeconds(2), ct);

Task.WaitAsync(timeout, ct) (.NET 8+) throws TimeoutException when the deadline passes โ€” the modern single-task time-box. The classic shape is Task.WhenAny(work, Task.Delay(timeout, ct)) and comparing the winner. Either way the underlying operation should also receive the token so it can actually STOP.

Fire-and-forget is a bug factory

An un-awaited Task drops exceptions and races process shutdown. If you truly can't await, capture the task and observe its completion explicitly with internal try/catch โ€” never a bare discarded call around something that can fail.

Check your understanding

  • When does ComputeAsync(1) actually begin running? (Synchronously, up to its first await.)
  • Who performs cancellation? (Your code, cooperatively โ€” the token only signals.)

โšก Now practice

Ready to Code
Async Practice: Fetch, Fan-out, CancelWrite cooperative async pipelines, prove fan-out ordering, unwrap exceptions, and time-box flaky work.
5 challenges ยท ยท ~30 min