Async/Await — Not About Multithreading

I think it’s safe to say that specialists of different ages mean different things when they mention multithreading in .NET. Those who started with .NET long ago thought in terms of threads. Those who began with async/await often understand it simply as managing waiting, not connected to thread management…

Let’s recall some different episodes from the history of synchronization.

How We Used to Wait: Blocking as the Norm

In early .NET (and not only there — I remember a very large C++ project with the same multithreading paradigm), waiting looked simple:

  • a thread enters a method,
  • the method does work,
  • the thread waits until the work is completed.

If the operation was long (I/O, network, disk):

  • the thread was blocked,
  • resources were idle,
  • UI froze,
  • the server “ate” threads.

Typical tools of that time:

  • lock
  • Monitor
  • WaitHandle
  • Thread.Sleep
  • manual thread management

Waiting = busy thread.

Asynchrony Before async/await: Pain and Callbacks

When it became clear that blocking threads is expensive, asynchronous APIs appeared:

  • Begin/End pattern,
  • callbacks,
  • events,
  • IAsyncResult.

Something like this:

  • an operation starts,
  • control returns immediately,
  • when completed a callback is invoked.

Technically — efficient.

Practically — unreadable and fragile.

Code:

  • lost linear structure,
  • was smeared across methods,
  • complicated error handling.

What async/await Changed

async/await didn’t add anything fundamentally new — it:

  • rewrote asynchrony into normal syntax,
  • preserved linear thinking,
  • removed manual continuation handling.

The key idea:

A thread doesn’t wait. The state of an operation waits.

When execution reaches await:

  1. if the result is ready — code continues,
  2. if not — the method is suspended,
  3. the thread is freed,
  4. the continuation runs later.

Where Are Threads — and Where Are Not

This is the most important point.

  • await does not create a thread
  • await does not guarantee another thread
  • await does not mean parallelism

It means only one thing:

“Continue execution when the result is ready.”

The thread for continuation:

  • may be the same
  • may be a different one
  • may not be explicitly noticeable at all

This is a detail of implementation, not part of the contract.

Why This Breaks Old Thinking

Previously it was simple:

  • one method → one thread → one sequence.

Now:

  • a method is a state machine
  • execution can:
    • be interrupted,
    • be resumed,
    • continue in a different context.

Therefore:

  • lock + await is dangerous,
  • blocking calls (.Result, .Wait()) are traps,
  • deadlocks become thinner and more insidious.

Asynchrony ≠ Parallelism

It’s important to clearly separate:

  • Asynchronynot waiting for an operation to complete,
  • Parallelismdoing multiple things at the same time.

async/await solves the first problem.

The second is solved by:

  • Task.Run,
  • thread pool,
  • parallel APIs.

Mixing them without understanding is a direct path to problems.

Why async/await Became So Popular

Because it:

  • doesn’t break the developer’s thought process,
  • looks like normal code,
  • scales better than blocking,
  • fits perfectly for I/O work.

And at the same time:

  • it doesn’t promise multithreading,
  • it doesn’t hide complexity — it pushes it to the runtime.

Conclusion

async/await is not a new model of parallelism.
It’s a new way to think about waiting.

Previously waiting meant:

“I am busy, wait.”

Now it means:

“I will come back when there is a reason to continue.”

And that’s why asynchronous code:

  • scales better,
  • is harder to debug,
  • requires a different architectural mindset.

Leave a Reply

Your email address will not be published. Required fields are marked *