P33 · Asyncio & Coroutines

the event loop, tasks & structured concurrency

Cooperative concurrency, built then used: model the event loop with generators, run real asyncio - gather, TaskGroups, timeouts - and bridge safely out to the blocking and CPU-bound world when the loop is the wrong tool.

await looks like a keyword you have to take on faith. It is not: it is the generator suspension from P29 with different syntax and a scheduler attached. This pillar makes that concrete by having you build the scheduler first — an event loop assembled out of generators, driving suspended frames in turn — so that by the time you write async def, nothing about it is unexplained. One cooperative loop outscales a pool of threads for I/O-bound work for a reason you will have implemented rather than been told.

With the loop understood, the real one comes out: async/await, gather for running many things concurrently, and cancellation, which is where naive async code usually breaks. Structured concurrency is the pillar's central discipline — a TaskGroup gives concurrent work a lifetime it cannot outlive, so a task cannot quietly survive the function that spawned it, a sibling failure cancels the rest rather than leaving orphans, and exception groups let you handle several failures without pretending there was only one. That is error handling you can reason about locally, which unstructured create_task never permits.

The last track is the one production breaks on: the loop is single-threaded, so any blocking call stalls everything, and the fix is not to avoid blocking work but to move it — to_thread and run_in_executor for the blocking or CPU-bound parts, keeping the loop free. Bounded queues close the pillar, because a producer faster than its consumer will otherwise exhaust memory silently; backpressure is the mechanism that makes the pipeline slow down instead of falling over.

Build the loopgenerators, scheduledRun the real oneawait, gather, cancelScope itTaskGroups, no orphansBridge outoffload blocking, bound queues
Build the scheduler before using it, then give concurrent work a lifetime and keep blocking calls off the loop.
P33 · Asyncio & Coroutines — TransformerLab