$ dotnet run5050core 1core 2thread pool1000 tasks · 2 threadsqueueworker 1worker 21var t1 = Task.Run(() => Sum(1, 50));2var t2 = Task.Run(() => Sum(51, 100));3int[] r = await Task.WhenAll(t1, t2);4Console.WriteLine(r[0] + r[1]);main threadrunningwaitingt1 · Task<int>–t2 · Task<int>–Sum(1, 50)Sum(51, 100)12753775line 45050jobjobjobjob
Front row: your code, the main thread and the two tasksBack row: the console, the CPU's two cores and the thread poolTokens: the jobs, their results, the rest of the method and the printed totalFront row: Program.cs, the main thread, and the two task cells t1 and t2. Back row: the console, a CPU with two cores, and the thread pool with its work queue and two worker threads. A turning gear is a thread that is running. Task.Run queues two jobs and returns two tasks at once; the two workers run the jobs in parallel; the tasks keep the results 1275 and 3775; await Task.WhenAll resumes on a pool thread and prints 5050. The picture is tidy on purpose. A real pool has as many workers as it decides (by default it adds them quickly up to the number of processors, then more slowly), and which worker runs which job, and in what order, can change from run to run. Only the results and their order in r are fixed.

Threads and the pool

  1. A thread is one path through your code: the operating system runs its instructions one after another on a CPU core. Every program starts with one, the main thread. This CPU has two cores, so two threads can run at the same moment.

Start two tasks

Wait for both

What happens

// step 1/10 · thread
thread       one path of execution: instructions run one after another
the OS       gives each thread turns (time slices) on a core
main thread  every .NET program starts with one
this CPU     2 cores → up to 2 threads run at the same instant

Program.cs is real C#. Below the four lines it defines the local function static int Sum(int lo, int hi) { int s = 0; for (int i = lo; i <= hi; i++) s += i; return s; }. dotnet run prints 5050. The picture is tidy on purpose. A real pool has as many workers as it decides (by default it adds them quickly up to the number of processors, then more slowly), and which worker runs which job, and in what order, can change from run to run. Only the results and their order in r are fixed.