Threads and the pool
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.
Starting a new thread costs time and memory, so .NET keeps a thread pool: worker threads that wait for jobs. Jobs line up in a queue, and each free worker takes the next one. Here the pool has two workers, one per core.
Start two tasks
Line 1 calls Task.Run(() => Sum(1, 50)). It puts that job in the pool's queue and returns at once with t1, a Task<int>: an object that stands for the work and, later, its int result. The main thread does not wait.
Line 2 queues a second job, Sum(51, 100), and gets back t2. Both lines took only a moment: the main thread queued work but did none of it. Each job now waits for a free worker.
Wait for both
Line 3 makes Task.WhenAll(t1, t2): one task that finishes when both have. await pauses the method right there. Nothing loops to check on the sums; the method simply resumes once both are done. Meanwhile the main thread waits.
Worker 1 takes the first job and worker 2 the second. Each runs on its own core, so both sums run at the same time: in parallel. Your code never chose a thread; the pool did.
When Sum returns, its task is marked done and keeps the value: t1.Result is 1275, the sum of 1 to 50, and t2.Result is 3775, the sum of 51 to 100. Both workers are free again.
Both tasks are done, so WhenAll completes with r = {1275, 3775}, in the order t1, t2. The method resumes after the await. A console app has no special thread to return to, so it carries on on a pool thread.
Line 4 adds the two parts and prints 5050: the sum of 1 to 100. It is the same answer as one loop, but the work was split across two threads on two cores.
Tasks are cheap; threads are not. Start a thousand Task.Run jobs and the pool runs them on a few threads, each worker taking job after job. A task is a piece of work and its result; a thread is a worker that runs it.
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.