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.