(In this answer, I will talk about .NET, as it is the first technology which came out with async/await
)
We use threads to parallelize CPU-bound tasks, and we use asynchronous IO to parallelize IO-bound tasks.
CPU - wise:
We all know that a thread per task is wrong. we don't want too many threads, because the context switches will freeze the entire system. we don't want too few of them, because we want the tasks to be finished as soon as possible. of-course, we're looking at some sort of a threadpool.
ThreadPool was the default way of scheduling an asynchronous task on the pre Task
epoch. but the threadpool had one sore problem - it was really hard to know when the asynchronous is finished, and what is the asynchronous result or exception are.
Then, came the Task
. not only does the task schedules a delegate on the thread pool, when the task is done, you can get the result or exception and continue working from them with Task.ContinueWith
.
IO - wise:
We all know that thread-per connection is a bad thing. if we want our optimized server to serve millions of requests per seconds, we can't just spawn a new thread for each new connection. our system will suffocate on context switching. So we use asynchronous IO. in the pre Task
epoch, we used BeginRead/EndRead
and BeginWrite/EndWrite
, which were error prone and just pain to work with - we had to work with terrible paradigm of event-driven programing
Then, came the Task
. we can initiate an asynchronous IO action, and receive the result or exception with Task.ContinueWith
. it made asynchronous IO much easier to work with.
Task
is the glue which bridges Asynchronous CPU tasks with asynchronous IO tasks. with one interface, we can schedule an asynchronous function and get the result with Task.ContinueWith
. No wonder programing with Task
s become so popular.
Task.ContinueWith
is highly unreadable and unwritable.
basically, chaining a task to a task to a task a to task... is a headache. as Node.js developers complains (even in JS async/await
will be standarized sometime in the future). async/await come to the rescue here. basically, the C# compiler does a neat voodo behind the scenes. in a nutshell, it takes everything that comes after await
and package it with state-machine which is called when the everything before await
is done. the compiler takes synchronous code (annotated with async
/await
) and does the ContinueWith
for you.
So, why use async
/await
+ Task
instead of a multi-threaded code?
async
/await
is thre easiest way of getting the asynchronous result or exception. (and believe me, I wrote asynchronous code in C++, C#, Java and Javascript, async
/await
is a paradise in that field.)
async
/await
works both with CPU-bound tasks and IO-bound tasks. same interface for two different but similar fields.
- If you want asynchronous IO, threads will not help you anyway.
Task
anyway is a IThreadPoolItem
and is scheduled to the .Net thread pool. async
/await
just removes the chaining hell away. back to step one -> multi thread code.
- Tasks +
async
/await
synchronizes the code flow for you. most developers are not system developers. they do not know the hidden costs of synchronization objects and techniques. in most cases, the implementation provided by the framework is faster than the average implementation that the average developer can think about. ofcourse if you really try, you can write something extremely customized for your needs hence more performant, but that doesn't apply for most developers.
- Depending on you programing language,
await
can be faster than a callback. Gor Nishanov is the original (Microsoft) developer who offered standarize await
in C++. In his 2015 lecture, he shows that the C++ version of await
is actually more performant than callback-style asynchronous networking IO. (switch to 39:30)
For specific questions :
Less work for the OS scheduler. True
False. async
/await
compiles to state machine. a task continuation invokes that state machine when its done. a task run on the threadpool anyway. async
/await
yields the same amount of scheduling as a multi threaded code / queuing a thread pool work. it's the simplicity you get which matters.
Less memory wasted for stack space. Or is it?
False. again, async
/await
compiles to a state machine. when invoked on task completion, it will use the same amount of stack memory for local varaiables. the continuation will anyway run on a thread (usually a thread-pool thread), so that argument is invalid.
Why is async considered better performing than multithreading?
When your code CPU-bound, there will be a little difference between between Tasks + async
/await
and pure multi-threaded code. In IO bound code, multi threading is the worst throughput you can have. Tasks + async
/await
will blow away any IO-bound-threadpool you can write your own. threads don't scale. usually (especially on Server side) you have both. you read some data from a connection (IO), then continue processing it on the CPU (json parsing, calculations etc.) and write the result back to the connection (IO again). Tasks + async
/await
are faster in this case than a pure multi threaded code.
It's the simplicity which makes async
/await
so appealing. writing a synchronous code which is actually asynchronous. if this is not "high level programing", what is?