What is async/await?

I've been pretty active on Hashnode and inactive here lately. So I'm going to repost an answer I gave on Hashnode here, because I'm lazy like that... This is a conceptual explanation, not a beginner tutorial.

Yes, async/await a way for the thread to not sit idle while waiting for something (usually IO like files, database or network).

When you read a file synchronously, your code does not do anything while the disk is busy. This means that for programs with a lot of IO, the speed of your application is limited by the IO, since you never get close to 100% CPU use.

The solution is to not wait for the IO to complete, but just get back to it when it's ready.

The "original" solution (for which the language does not need to change, which is perhaps the reason for its popularity), is to provide some function that will be run when the IO is ready, then do the IO in the background while your program continues.

(The other obvious solution is to have many threads, but race conditions are horrible, and thread switching isn't as fast jumping to different code in the same thread).

For the computer, this totally solves the problem. It doesn't need to wait for IO, can reach 100% CPU, gets a sign when the IO is ready... Perfect.

But for humans, it quickly becomes messy. Code is much easier to read if the lines are in the same order as the execution. But if you write something like (pseudo-language)

print "start of program!"
open("my_file.txt").read(when_ready = function(content) {
        print "file read!"
print "after!"

This probably prints

start of program!
file read!

Additionally, it's hard to get content out of the function. You basically have to do the entire rest of the program from that function.

So to make this whole "don't wait for IO" more human-friendly. So some languages introduce async/await and do all that bookkeeping in the background. So the above becomes.

print "start of program!"
content = await open("my_file.txt").read()
print "file read!"

Looks much better right? Note though, that the flow gets interrupted at await, so your program state may have changed before and after the file are read. I.e.

print "start of program!", global.my_nr
content = await open("my_file.txt").read()
print "file read!", global.my_nr

May show

start of program! 1
file read! 2

But this happens only at await. So it's much less invasive than 'real' threading.

Then there's fancy stuff like waiting for several files to all be ready, or for the first of several files to become available. But the basic idea is the same: compiler magic to make callbacks convenient.

It's not just for IO. You can also do e.g. heavy CPU stuff in an awaitable task, if you want your main thread to go on while it runs.

Note that asynchronicity in this way is not typically parallel. Okay, it is a little: the file reading and the main thread happen in parallel.

But although different 'flows' of the main thread are intertwined, they are never running at the same time. One piece runs until await #1, then another one runs until await #2, and await #1 continues.

But at the other hand, there is nothing stopping anyone from having multiple threads doing this await switching. (Well, Python's GIL makes it unattractive... but you can!) Or even to distribute the await flows over threads dynamically. But then you get all the problems of multithreading back, so many (like Node) don't do it.

To be technical: `async`/`await` is a way to have continuation passing style behaviour but pretend that you're writing normal-looking code with returns. It is one of several ways to solve IO-bound performance, perhaps the most fast and elegant way if language- and library support are available.


Topics: #coding #python #programming-languages #performance #async


No comments yet

You need to be logged in to comment.