C# · Course navigation

C#: running tasks in parallel with Task.WhenAll

Start independent computations as tasks and await all of their results.

On this page
Before you start

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

C#
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());
  }
}
Output
125250 375250 500500

Follow the code

Open the walkthrough to inspect the example one step at a time.

Show guided walkthrough
Guided walkthroughReady
1using System;
2using System.Linq;
3using System.Threading.Tasks;
4
5class Program {
6 static async Task Main() {
7 int[] data = Enumerable.Range(1, 1000).ToArray();
8
9 Task<long> first = Task.Run(() => data.Take(500).Sum(x => (long)x));
10 Task<long> second = Task.Run(() => data.Skip(500).Sum(x => (long)x));
11
12 long[] parts = await Task.WhenAll(first, second);
13 Console.WriteLine(parts[0] + " " + parts[1] + " " + parts.Sum());
14 }
15}

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
Prepared trace of the published program. Some rows group repeated iterations.
Step / lineWhat happensState after stepOutput 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.

Further reading