Why should I prefer single 'await Task.WhenAll' over multiple awaits?
Data Structures & Algorithms practice on Codemia
Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.
Introduction
await Task.WhenAll(task1, task2, task3) runs all tasks concurrently and waits for all to complete. Multiple sequential awaits (await task1; await task2; await task3;) run tasks one after another. If the tasks are independent (no data dependencies), Task.WhenAll is faster because tasks overlap. It also handles exceptions better — Task.WhenAll collects all exceptions into an AggregateException, while sequential awaits stop at the first failure and lose the others.
The Performance Difference
The key is to start all tasks before awaiting any of them. Task.WhenAll then waits for all of them to finish.
Getting Results from Task.WhenAll
Exception Handling
When to Use Sequential Awaits
Sequential awaits are correct when tasks depend on each other:
Real-World Example: Parallel API Calls
Task.WhenAll with Timeout
Common Pitfalls
- Awaiting each task immediately instead of starting all first:
await Task1Async(); await Task2Async();runs sequentially. To run concurrently, store the tasks in variables first (var t1 = Task1Async(); var t2 = Task2Async();) and thenawait Task.WhenAll(t1, t2). The task starts when you call the method, not when you await it. - Using
Task.WhenAllfor dependent tasks: If task2 needs the result of task1, they cannot run concurrently. UsingTask.WhenAllin this case either fails (because task2 needs input that does not exist yet) or requires restructuring the dependency chain. - Ignoring exceptions from individual tasks:
await Task.WhenAll(...)throws only the first exception. If multiple tasks fail, check each task's.Exceptionproperty individually to log all failures. TheAggregateExceptionis accessible via theTask.WhenAlltask's.Exceptionproperty. - Using
.ResultbeforeTask.WhenAllcompletes: Accessing.Resulton a task that has not completed blocks the calling thread synchronously. Alwaysawait Task.WhenAll(...)first, then access.Resulton the completed tasks. - Creating tasks in a loop without collecting them:
foreach (var item in items) await ProcessAsync(item);processes items sequentially. Collect tasks into a list first:var tasks = items.Select(ProcessAsync).ToList(); await Task.WhenAll(tasks);for concurrent processing.
Summary
- Use
await Task.WhenAll(...)for independent tasks to run them concurrently (total time = slowest task) - Use sequential
awaitonly when tasks depend on each other's results - Start all tasks before awaiting any — the task begins when you call the async method
Task.WhenAllcollects all exceptions; sequential awaits stop at the first failure- Access
.Resultonly afterTask.WhenAllhas completed to avoid synchronous blocking
Related reading
- Why should I use var instead of a type?
- Why should preprocessing be done on CPU rather than GPU?
- Why shouldn't I use PyPy over CPython if PyPy is 6.3 times faster?
- Why shouldn't I use PyPy over CPython if PyPy is 6.3 times faster?
- Why should Java ThreadLocal variables be static
- Why should wait always be called inside a loop
- Why SortedSetT.GetViewBetween isn't Olog N?
- Why static classes cant implement interfaces?

DSA Fundamentals
Master algorithmic patterns and data structures through hands-on LeetCode-style problems - from arrays and hashing to dynamic programming and advanced graphs.
View the courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
Data Structures & Algorithms practice on Codemia
Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.