I admit it: I like Python's asyncio

The useful API is actually nice and small

Python's asyncio gets a fair bit of bad press. Some of it I agree with, but there is one aspect of asyncio I like: the API needed for a lot of common tasks is actually fairly small and clear.

Here's a small but fairly realistic program. It creates a pool of HTTP connections, and uses this to make two concurrent chains of requests.

import asyncio
import httpx

async def async_main():
    async with httpx.AsyncClient() as client:
        task_1 = asyncio.create_task(make_http_requests(client, {'some': 'data'}))
        task_2 = asyncio.create_task(make_http_requests(client, {'more': 'data'}))
        await asyncio.gather(task_1, task_2)

async def make_http_requests(client, data):
    resp_1 = await client.post('https://postman-echo.com/post', data=data)
    resp_2 = await client.post('https://postman-echo.com/post', data=resp_1.json())

asyncio.run(async_main())

Tasks

A task is an asyncio thread of execution. Multiple tasks can run concurrently: typically when a task is waiting for some data to be sent/received on the network, other tasks can progress. This is in fact the whole point of asyncio.

In the above example, asyncio.run creates the first task, and during that task, each call to asyncio.create_task creates another task that runs concurrently.

await my_function()

If you see await my_function() it means two things:

  • the current task will schedule my_function to run, and wait for it to finish;
  • in the meantime other tasks can progress, typically during network I/O.

asyncio.gather

To wait for multiple tasks to finish, pass them to asyncio.gather, and await the result.

async def my_function():

A function declared as async is very similar to a regular function, but just calling it won't do anything: it then needs to be scheduled. This is done by either awaiting its result, or passing it to another function such as create_task that will schedule it for you.

Some don't like having to write async and await, but in complex multi-task programs, I find it useful to be clear which functions allow other tasks to progress, and which don't.

async with

async with is like a regular context manager's with, except that during entry and exit, other tasks may progress. It's commonly used to create and destroy pools of network connections, often a part of HTTP or database clients.


That's it! There are more parts to the API, but for a lot of common use-cases, you don't need them.