# Who Waits When You await? (Hint: Not your Threads)

4 min read
Table of Contents

I’ve always been troubled with async in languages like C#, Javascript and Rust. The question that always haunted me was: How does data come from I/O? If no threads are involved how does the data even get read?

That question led me on a journey to find how does async work. Here’s the mental model that I built along the way.

Many developers assume that await means the work happens on a background thread. This intuition is understandable, but it’s incorrect. Worse, it leads to misunderstandings about performance and when async actually helps.

await in C# does not mean “run this on a background thread”. It means “pause here, and resume when the operation completes”.

What await actually does

At language level, async and await is not a threading feature, it’s a control-flow transformation performed by the compiler.

When you write something like this:

async Task<int> GetValueAsync()
{
await Task.Delay(1000);
return 42;
}

The compiler rewrites this method into a State Machine. Think of this like a Bookmark system.

  1. The Snapshot: When an await is reached, the compiler takes a snapshot of all local variables.
  2. The Bookmark: It places a “bookmark” in the code and registers a callback (continuation).
  3. The Exit: The thread is released. It doesn’t sit there; it literally leaves the method and returns to the ThreadPool to handle other work.
  4. The Return: When the I/O finishes, the “bookmark” is found, the state is restored, and thread(any available thread) continues execution.

If no thread is waiting, who is?

If no thread is blocked, what actually drives the I/O operation? Enter IOCP (I/O Completion Ports).

On Windows, asynchronous I/O is built upon I/O completion ports (IOCP).1 IOCP is not a thread pool itself, and it doesn’t execute your code.2 It is a kernel mechanism for tracking I/O operations.

The flow looks like this:

  1. You initiate a asynchronous I/O operation (eg. socket read, file read).
  2. Your request hits a Device Driver. The CPU tells the hardware(like your Network Card): “Put the data here and let me know when it’s done.”
  3. The kernel registers the operation and returns immediately. No thread is blocked.
  4. When the hardware finishes, it sends an interrupt. The kernel enqueues a completion notification.
  5. A Thread from the ThreadPool picks up that notification and executes your “bookmark” (the continuation).

So, a thread is involved but threads run the work i.e, the continuation, not the waiting part. This is an important distinction.

Async/await improves throughput not by making work faster, but by decoupling CPU execution from I/O waiting. By avoiding thread blocking, the same pool of threads can keep the CPU busy with useful work while the kernel handles outstanding I/O.

Why even use async

There might be another question looming: If it doesn’t make my work faster why even use async?

Imagine a webserver with 8 threads:

  • Synchronous: 8 users request a slow file. All 8 threads are now blocked, starting at the disk. The 9th user is rejected, even though the CPU is idle and doing nothing but waiting.
  • Asynchronous: Those 8 threads initiate the I/O and immediately return to the ThreadPool. They can now handle the next 8 users while the disk does its thing.

Async / await helps to minimize the CPU idle time, the most precious resource is the clock-cycles. If you cpu is doing nothing, then it’s literally causing you slow downs.

Key Takeaways

  • await = suspend and resume, not “run on background thread.”
  • The Kernel handles waiting; Threads handle the work.
  • Scalability comes from preventing thread starvation, not from the individual operation speed.

It’s the ultimate “work smarter, not harder” move for the CPU.

And no threads were harmed in the making of this await.

Footnotes

  1. On Linux and MacOS, the async I/O depends on epoll, io_uring and kqueue(MacOS/BSD).

  2. IOCP by itself is not a ThreadPool but .NET manages an IOCP ThreadPool to handle I/O. This is a bigger topic, maybe for a future post by itself.

My avatar

Thanks for reading my blog post! Feel free to check out my other posts or contact me via the social links in the footer.


More Posts