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 await
ing 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.