Tasks and threads
Understand what a Task represents, how it relates to threads, and how to run work concurrently.
11 min read
You will learn
- Explain the difference between a thread and a task
- Describe the thread pool
- Start work with Task.Run and wait for delays with Task.Delay
- Combine tasks with Task.WhenAll and Task.WhenAny
Waiting is the normal case
Most of what a backend does is waiting: for the database, for another HTTP service, for a file, for a message. While it waits, the CPU has nothing to do. Asynchronous programming is about not wasting resources during those waits — and about doing several things at once.
Threads
A thread is the operating system's unit of execution: it has its own stack and runs code on a CPU core. Threads are relatively expensive — each reserves about 1 MB of stack, and switching between thousands of them costs CPU time.
Rather than creating threads, .NET keeps a thread pool: a set of reusable worker threads that pick up queued work items. ASP.NET Core handles each incoming request on a pool thread.
Tasks
A Task represents an operation that will complete in the future; Task<T> also produces a result of type T. A task isn't a thread:
- A CPU-bound task (
Task.Run(() => Compute())) runs on a pool thread. - An I/O-bound task (
httpClient.GetStringAsync(url),Task.Delay(1000)) usually uses no thread at all while waiting — the OS or a timer signals completion. - A task can already be completed (
Task.FromResult(42)).
Task<string> download = httpClient.GetStringAsync("https://example.com");
// ... do other things while the download is in flight ...
string html = await download; // get the result when you need itThe thread pool under load
Here's why this matters for servers. Compare a server whose code blocks while the database works with one that awaits:
Threads: blocking vs async/await
Each request: 1 unit of CPU work, a 4-unit database call, then 1 more unit of CPU
Requests
Thread pool
All requests finished at
t = 12
async: 7
Average response time
9.0
async: 6.5
Thread time wasted blocked
16 units
async: 0
var rows = db.Query(...) or .Result), a thread sits idle while the database works — the striped cells. Other requests queue up even though the CPU has nothing to do. Switch to async / await to compare.With blocking code, threads sit idle holding requests hostage. With await, a few threads serve many concurrent requests. That's the core reason ASP.NET Core APIs are async all the way down.
Running work concurrently
Start several operations, then wait for all of them:
Task<User> userTask = GetUserAsync(id);
Task<List<Order>> ordersTask = GetOrdersAsync(id);
Task<decimal> balanceTask = GetBalanceAsync(id);
await Task.WhenAll(userTask, ordersTask, balanceTask); // all three in flight at once
var user = userTask.Result; // safe here: the task has already completed| Method | Completes when |
|---|---|
Task.WhenAll(tasks) | All tasks complete (and gives all results for Task<T>) |
Task.WhenAny(tasks) | Any task completes — e.g. "first response wins" or timeouts |
Task.Delay(ms) | After the delay, without blocking a thread |
Task.Run(work) | The CPU work queued to the thread pool has finished |
Don't block on tasks
task.Result and task.Wait() block the current thread until the task finishes — wasting it, and in some environments causing deadlocks. Use await instead. (Reading .Result after await Task.WhenAll is fine, as above, because the tasks are already done.)
Try it
C# runtime not loaded
Key takeaways
- Threads are expensive OS resources; the thread pool reuses a small number of them.
- A Task is a future result — often with no thread involved while it waits.
Task.WhenAllruns independent operations concurrently;Task.WhenAnytakes the first to finish.- Use
Task.Runfor CPU-bound work only, and never block on tasks with.Resultor.Wait().
Quiz
4 questions · 3 correct to pass1.What does a Task<int> represent?
2.Does `await Task.Delay(1000)` keep a thread busy for a second?
3.Three independent calls each take 1 second. How long does `await Task.WhenAll(a, b, c)` take, if they were all started first?
4.When is Task.Run appropriate?
To complete this lesson
- ○ Pass the quiz (3/4 correct)