Quick notes on Python asyncio

This post focus on Python asynchronous execution model. Code shown in examples requires Python 3.7+ to work. Over the last few years, asynchronous programming has become an important paradigm and an effective tool for programmer to handle distributed computing.

Asynchronous Programming

The idea behind asynchronous execution is not new and fairly intuitive. For an example, imagine you are building a lightweight web server that helps a popular restaurant handle online ordering and many customers might use it at the same time. For a sequential execution model the server will have to wait for each customer to finish the order before it can handle the next request. With sync execution model, the server can launch an hanlder for one customer and immediately returns back to the main loop to find out if there are more pending request while that customer is browsing the menu. After that customer has finished his order, he will inform the server by pressing an confirm button, which brings back your program to process the order. In Python, the above workflow can be roughly described by following snippet:

async accept_request(request):
  order = await wait_for_customer(request)
  handle_order(order)

In the example, await means the function should pass the control back to caller until wait_for_customer has returned. To use the Python keyword await one must specify the function as async.

Python Asynchronous Programming Basic

Quoting from the official document, a simplest example is shown below:

import asyncio

async def main():
  print("Hello")
  await asyncio.sleep(1)
  print("World")

asyncio.run(main())

There’s a lot of things going on in this example, let’s walk them through. First we import the asyncio module, which is a builtin module of Python standard library and provides basic facilities for asynchronous programming. Next the main function is prefixed by a keyword async. Function like this is called Coroutine. Calling coroutine doesn’t return the actual result. To obtain the result (including side effect), one must use some other mechanisms like asyncio.run. We might want to do a bit more than just calling 1 function, async functions can be chained:

import asyncio

async def handle_request(request: int):
  asyncio.sleep(request)

async def main():
  for re in [1, 3, 5, 7, 9]:
    await handle_request(re)

asyncio.run(main())