Why doesn't await on Task.WhenAll throw an AggregateException?
Interview Questions practice on Codemia
Over 8,000 real interview questions from top companies, searchable by company and role.
Introduction
Many developers expect await Task.WhenAll(...) to throw AggregateException, but await follows different exception semantics. The language unpacks task failures to keep async code readable and close to synchronous try and catch flow. The aggregate is still present on the task object, but not always thrown directly by await.
How await Handles Exceptions
await observes the awaited task and rethrows failures in an unwrapped form when possible.
This behavior is by design so ordinary async error handling does not require aggregate unwrapping in common cases.
What Task.WhenAll Produces
Task.WhenAll returns one task that completes when all inner tasks complete. If multiple inner tasks fail, the returned task stores all failures in its Exception.InnerExceptions collection.
So the aggregate exists, but await surfaces a simplified throw path.
Why This Design Is Useful
If every awaited failure always threw AggregateException, most async catch blocks would include boilerplate unwrapping logic for single-failure operations. The current model optimizes everyday readability while still allowing advanced inspection.
You get two levels:
- simple thrown exception from
awaitfor primary control flow, - full failure list from the stored aggregate when needed.
Difference From .Wait() And .Result
Blocking APIs use older task consumption semantics.
This is one reason async codebases should avoid .Wait() and .Result on asynchronous tasks.
Cancellation And Mixed Outcomes
When tasks include cancellation and faults together, final status can be faulted or canceled depending on combined outcomes. For incident analysis, capture:
- overall task status,
- first thrown exception observed by
await, - full
InnerExceptionslist when present, - cancellation token state.
This gives complete telemetry for parallel workflows.
Practical Logging Pattern
In service code, preserve the WhenAll task reference, then log all inner failures in catch block.
This pattern avoids losing secondary failures.
Unit Testing This Behavior
When documenting team conventions, add tests that assert both the thrown exception from await and the full InnerExceptions list on the aggregate task. This prevents future refactors from silently reducing error visibility in parallel execution paths.
Common Pitfalls
- Assuming
awaithides additional failures fromTask.WhenAll. - Discarding
WhenAlltask reference and losing aggregate inspection path. - Using
.Resultin async paths and introducing aggregate wrapping plus deadlock risk. - Logging only first observed exception in parallel workloads.
- Ignoring cancellation state when diagnosing mixed outcomes.
Summary
- '
awaitfavors readable exception flow and often rethrows a single exception type directly.' - '
Task.WhenAllstill captures all failures in aggregate task state.' - Keep a reference to the returned task to inspect
InnerExceptions. - Prefer
awaitover blocking task consumption APIs. - Log aggregated failures explicitly when debugging parallel task orchestration.
Related reading
- Why doesn't 'await' wait for axios request completes?
- Why doesn't JavaScript support multithreading?
- Why in cluster PSQL with 3 node one node - sync_state sync and next node sync_state async?
- Why invoke Thread.currentThread.interrupt in a catch InterruptException block?
- Why doesn't C infer my generic types?
- Why doesn't C support the return of references?
- Why doesn't Git ignore my specified file?
- Why doesn't incrementing Nullableint throw an exception?

OOD Fundamentals
Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.
View the courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
Interview Questions practice on Codemia
Over 8,000 real interview questions from top companies, searchable by company and role.