C#: running tasks in parallel with Task.WhenAll
Start independent computations as tasks and await all of their results.
On this page
C# async and await, and LINQ.
- Start independent computations as tasks and await all of their results.
- Predict the sample output and explain the state changes.
- Complete the practice task and check the boundary cases.
The concept
A Task represents work that is running or will run, and Task<T> will eventually produce a value of type T. Task.Run schedules a delegate on a thread-pool thread, so two calls can run at the same time on different cores.
Task.WhenAll returns a task that completes when all the given tasks have, and awaiting it yields their results as an array. The two halves of the sum read the same array but write nothing shared, so no locks are needed. async Task Main allows await in the entry point.
Code example
using System;
using System.Linq;
using System.Threading.Tasks;
class Program {
static async Task Main() {
int[] data = Enumerable.Range(1, 1000).ToArray();
Task<long> first = Task.Run(() => data.Take(500).Sum(x => (long)x));
Task<long> second = Task.Run(() => data.Skip(500).Sum(x => (long)x));
long[] parts = await Task.WhenAll(first, second);
Console.WriteLine(parts[0] + " " + parts[1] + " " + parts.Sum());
}
}125250 375250 500500
Follow the code
Open the walkthrough to inspect the example one step at a time.
Show guided walkthrough
using System;using System.Linq;using System.Threading.Tasks; class Program { static async Task Main() { int[] data = Enumerable.Range(1, 1000).ToArray(); Task<long> first = Task.Run(() => data.Take(500).Sum(x => (long)x)); Task<long> second = Task.Run(() => data.Skip(500).Sum(x => (long)x)); long[] parts = await Task.WhenAll(first, second); Console.WriteLine(parts[0] + " " + parts[1] + " " + parts.Sum()); }}Follow the example
Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.
View the complete dry-run table
| Step / line | What happens | State after step | Output so far |
|---|---|---|---|
| The numbers 1 to 1000. | data = 1..1000 | — | |
| The first task sums the first 500 numbers on a pool thread. | first = sum of 1..500 | — | |
| The second task sums 501 to 1000 at the same time. | second = sum of 501..1000 | — | |
| Wait for both tasks without blocking a thread. | second = sum of 501..1000 | — | |
| 125250 + 375250 = 500500. | total = 500500 | 125250 375250 500500 |
Behind the scenes
How C# processes this code
C# compiles to code for the .NET runtime, which invokes the program entry point.
Cost and common mistakes
O(n) work in total, with about O(n / p) elapsed time on p cores.
- Calling .Result or .Wait() on a task can deadlock in some contexts. Prefer await.
- Task.Run suits CPU-bound work. For waiting on I/O use truly asynchronous APIs, not threads blocked on a call.
- If a task throws, the exception is raised when you await it.
Try it yourself
How would you use four tasks?
Reveal answer and reasoning
Split the range into four slices with Skip and Take, create four tasks, and await Task.WhenAll with all four.