Programming with AI
ENRU
2.10

Asynchrony

async, await, and why a bot needs them

What it is

Asynchrony is a way of not standing idle while the program waits for something. An API request takes half a second, and ordinary code just stands there for all of it. Asynchronous code gets other things done meanwhile.

For a bot this is essential: while one user waits for the weather, the rest must still get answers. To read the code it is enough to understand two words: async and await.

What it looks like in code

import asyncio
import time


async def fetch_city(name: str) -> str:
    await asyncio.sleep(0.3)          # as if it were a network request
    return f"{name}: +4"


async def main() -> None:
    t = time.perf_counter()
    for city in ["Oslo", "Bergen", "Tromsø"]:
        print(await fetch_city(city))
    print(f"one at a time: {time.perf_counter() - t:.1f} s")

    t = time.perf_counter()
    results = await asyncio.gather(
        *(fetch_city(c) for c in ["Oslo", "Bergen", "Tromsø"])
    )
    print(f"all at once: {time.perf_counter() - t:.1f} s")


asyncio.run(main())

Output:

measured
one at a time: 0.9 s
all at once: 0.3 s

Three requests of 0.3 seconds each: in sequence, 0.9; together, 0.3. That is the whole benefit of asynchrony, measured.

Three words

async def — the function is asynchronous. The main consequence: calling it does not run it; it returns a promise object. To get the result you need await.

await — "wait for the result, and while we wait, go do other things". It can only appear inside an async def.

asyncio.run(main()) — the launch. The point where the asynchronous world begins.

Where it pays and where it doesn't

Asynchrony helps where the program waits: network, disk, database. Where the program computes, there is nothing to gain — the processor is busy and there is nothing to switch to.

HelpsDoesn't help
API requestsmultiplying matrices
database workimage processing
reading and writing filesparsing a large text
waiting for messages from Telegramany heavy computation

The most common mistake: a forgotten await

data = fetch_city("Oslo")        # forgot await
print(data)

This prints something like <coroutine object fetch_city at 0x...> plus a coroutine was never awaited warning. The function never ran at all.

A blocking call in asynchronous code

The second common trouble is an ordinary synchronous library inside an async def:

async def fetch_city(name: str) -> str:
    r = requests.get(url)        # blocks everything!
    return r.text

requests is synchronous: for the duration of the request the whole program stops, and the bot stops answering anyone. The asynchronous replacements are aiohttp, or httpx in async mode. Same with databases: sqlite3 blocks, aiosqlite does not.

The sign while reading: inside an async def, a call without await to something that goes to the network, the disk or the database.

What to tell the AI

Prompt
Are there any blocking calls in this asynchronous code?
Name the lines and what to replace them with.
Prompt
Do these three requests run one after another or together?
If one after another, show me how to do them together.
Prompt
Rewrite this so it doesn't block the event loop.
You may change libraries, but say which ones you are adding.

What to look for

A forgotten await. The most common one. Found by coroutine in the output.

A synchronous library inside an async def. requests, time.sleep, a plain open on a big file — all of them stop the program entirely. time.sleep(1) has to become await asyncio.sleep(1).

Sequential awaits where one batch would do. A loop with an await inside runs the requests one after another. If they are independent, combine them with asyncio.gather — as in the example, three times faster.

Asynchrony where it isn't needed. If the program never waits for anything, async only adds complexity. The AI sometimes makes everything asynchronous.

Cheat sheet

  • async def does not run when called — it needs await.

  • await only works inside an async def.

  • It pays where the program waits: network, disk, database. Not for computation.

  • <coroutine object ...> — a forgotten await.

  • requests and time.sleep inside an async def block everything.

  • Combine independent requests with asyncio.gather.