Task.WhenAll vs Parallel.ForEachAsync - Which approach is best and why?
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
These two APIs solve related but different problems. Task.WhenAll is a coordination primitive for tasks you already created, while Parallel.ForEachAsync is a work-dispatching API that processes an input sequence with built-in concurrency control.
Use Task.WhenAll When the Tasks Already Exist
Task.WhenAll works best when you have a known set of asynchronous operations and want to await their combined completion.
This is concise and natural when the number of operations is small or already known. Each task starts immediately, and Task.WhenAll simply waits for them all.
The downside is that it is easy to create too many tasks at once. If you project a massive input set into tasks and then await them all, you may create more concurrency than the system or the downstream service can handle.
Use Parallel.ForEachAsync for Bounded Fan-Out
Parallel.ForEachAsync is designed for iterating over a sequence while limiting how many operations run at the same time.
This is a better fit when you have many items and need the runtime to throttle concurrency automatically.
The Real Decision: Workload Shape
The best choice depends less on micro-benchmarks and more on the shape of the workload.
Choose Task.WhenAll when:
- you already have the tasks
- the count is reasonable
- you want the natural array of results back
Choose Parallel.ForEachAsync when:
- you have an input collection rather than pre-created tasks
- you need bounded concurrency
- each item is processed more for side effects than for collecting return values
That is why asking which one is universally "best" misses the point. Each API is optimized for a different pattern.
Result Handling and Error Behavior
Task.WhenAll naturally returns results when the tasks produce values. That makes it convenient for request fan-out patterns.
Parallel.ForEachAsync is better when each item is processed independently and you manage any result collection yourself, typically with a thread-safe structure.
Both propagate failures, but the code shape is different. With Task.WhenAll, you tend to think in terms of task aggregation. With Parallel.ForEachAsync, you think in terms of processing one item at a time under a concurrency limit.
CPU Work Versus I/O Work
Many developers assume Parallel.ForEachAsync is only for CPU-bound work because of the word "Parallel." That is not true. It is also useful for async I/O when you want controlled fan-out, such as making API calls against a service that should not receive hundreds of concurrent requests.
Likewise, Task.WhenAll is not automatically wrong for I/O. It is often the cleanest choice for a handful of independent requests. The real problem appears only when the number of tasks explodes.
Common Pitfalls
- Creating thousands of tasks and handing them to
Task.WhenAllwithout any concurrency limit. - Using
Parallel.ForEachAsyncwhen you already have a small, fixed set of tasks thatTask.WhenAllwould express more clearly. - Ignoring
MaxDegreeOfParallelism, which removes the main operational advantage ofParallel.ForEachAsync. - Assuming one API is always faster instead of looking at memory pressure, downstream load, and result-collection needs.
- Forgetting cancellation handling for long-running or high-volume workloads.
Summary
- '
Task.WhenAllwaits for a set of tasks you already created.' - '
Parallel.ForEachAsyncprocesses a sequence while controlling concurrency.' - Use
Task.WhenAllfor small or known task sets that should start together. - Use
Parallel.ForEachAsyncfor larger collections where bounded fan-out matters. - Pick the API that matches the workload shape, not the one with the more impressive name.
Related reading
- Tell when Job is Complete
- Tensoflow CPU Allocation exceeds 10 of system memory
- Tensor flow toggle between CPU/GPU
- Tensorboard Event File Is Large and Growing
- TcpClient vs Socket when dealing with asynchronousy
- Technically, why are processes in Erlang more efficient than OS threads?
- TCPClient vs Socket in C
- TensorFlow.keras namespace not recognized by PyLance in Visual Studio Code

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.