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:
lockMonitorWaitHandleThread.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:
- if the result is ready — code continues,
- if not — the method is suspended,
- the thread is freed,
- the continuation runs later.
Where Are Threads — and Where Are Not
This is the most important point.
awaitdoes not create a threadawaitdoes not guarantee another threadawaitdoes 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+awaitis dangerous,- blocking calls (
.Result,.Wait()) are traps, - deadlocks become thinner and more insidious.
Asynchrony ≠ Parallelism
It’s important to clearly separate:
- Asynchrony — not waiting for an operation to complete,
- Parallelism — doing 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.