They don't run in parallel, they take turns. When progress is blocked for the running Task, it stores its state and yields control to a ready Task. It's cooperative multitasking, not true parallelism.
Threads operate on the sample principle. However there are several key differences I'd like to highlight.
First, simply because async
/await
aren't OS threads:
- Tasks won't see different Thread IDs
- Thread-local storage is not automatically context-switched when a Task yields.
Secondly, differences in behavior:
async
/await
use cooperative multitasking, Win32 threads use pre-emption. So it's necessary that all blocking operations explicitly yield control using the async
/await
model. So you can end up blocking an entire thread and all its Tasks by making a blocking call to a function not written to yield.
- Tasks won't be executed in parallel on a multiprocessing system. Since re-entrancy is controlled, this makes keeping data structures consistent a whole lot easier.
As Stephen points out in a comment, you can get simultaneous execution in multiple OS threads (along with all the complexity and potential race conditions) if you use a multithreaded synchronization context. But the MSDN quotes were about the single-threaded context case.
Finally, other places this same design paradigm is used, you can learn a lot about good practices for async
/await
by studying these:
- Win32 Fibers (uses the same call style as threads, but cooperative)
- Win32 Overlapped I/O operations, Linux aio
- Coroutines