Week 13 - asyncio Foundations: Event Loop, Tasks, Coroutines¶
13.1 Conceptual Core¶
- An async function is a function that returns a coroutine object. Awaiting yields control to the event loop. The event loop drives many coroutines, switching at every
await. - The cardinal sin: blocking the event loop. A single
time.sleep(1), sync DB call, or CPU-heavy loop in a coroutine stalls every other task. This is the most common production asyncio bug. - Task vs. Coroutine. A coroutine is a description; a
Taskis a coroutine scheduled on the loop.await cororuns it inline;asyncio.create_task(coro)runs it concurrently and returns a handle.
13.2 Mechanical Detail¶
asyncio.run,asyncio.create_task,asyncio.gather,asyncio.wait,asyncio.as_completed,asyncio.wait_for,asyncio.TaskGroup(3.11+, the way to write structured concurrency since 3.11).async with,async for, async generators,__aenter__/__aexit__,__aiter__/__anext__.asyncio.Queue,asyncio.Lock,asyncio.Event,asyncio.Semaphore,asyncio.Condition. None are thread-safe; for thread-safe inter-loop comm, useasyncio.run_coroutine_threadsafeorjanus.- Cancellation is cooperative and exception-based:
task.cancel()injectsCancelledErrorat the nextawait. Code that catchesExceptionswallowingCancelledErroris the asyncio anti-pattern; in 3.11+,CancelledErroris no longer a subclass ofException- but old code remains. - Timeouts:
async with asyncio.timeout(5):(3.11+) is the idiomatic form.asyncio.wait_foris older and has subtle cancellation pitfalls. loop.run_in_executor(None, blocking_fn, args): the escape hatch for blocking calls. Use for legacy DB drivers, file I/O if not usingaiofiles, and CPU work.
13.3 Lab - "The Crawler That Doesn't Lie"¶
- Build an async HTTP crawler with
httpx.AsyncClientand aTaskGroup. Limit concurrency with aSemaphore(N). - Add a 5-second per-request timeout using
asyncio.timeout. Verify cancellation propagates cleanly to thehttpxrequest. - Inject a deliberately blocking
time.sleep(2)somewhere. Detect it withasyncio.get_event_loop().slow_callback_duration = 0.1and the resulting log warnings. - Replace the blocker with
asyncio.sleep. Confirm viapy-spy dumpthat the loop never stalls.
13.4 Idiomatic & Linter Drill¶
- Enable
ruffASYNCrule set in full. Catch every blocking call insideasync def.
13.5 Production Hardening Slice¶
- Add
aiomonitororaiodebugto your dev environment. Add a request-idContextVarand structured logging that propagates acrossawaitboundaries.