Week 14 - Structured Concurrency, Cancellation, ExceptionGroups, anyio¶
14.1 Conceptual Core¶
- Structured concurrency: a parent task does not exit until its children have finished. No orphaned tasks, no leaked work.
asyncio.TaskGroup(3.11+) andanyio.create_task_groupare the canonical implementations; both inspired by Trio. - Cancellation must be honored. A coroutine that catches
BaseException(or worse,Exceptionin <3.11) and ignores it breaks structured concurrency. The contract: if you must catch, re-raiseCancelledError. ExceptionGroup(PEP 654, 3.11+): when multiple sibling tasks fail, you get anExceptionGroupcontaining all of them.except* ValueError:matches the subset.
14.2 Mechanical Detail¶
anyioas the portable abstraction: works onasyncioortriobackends, gives youcreate_task_group,move_on_after,fail_after,to_thread.run_sync,from_thread.run. If you write libraries, preferanyioover rawasyncio.contextvars.ContextVar: the async-safe replacement forthreading.local. Used by tracing libraries, request-id propagation, FastAPI dependencies.- Backpressure: bounded
asyncio.Queueis your friend. Unbounded queues are how async services OOM in production. - The
eager_task_factory(3.12+): start tasks eagerly when possible, reducing scheduling overhead.
14.3 Lab - "The Fan-Out That Cleans Up After Itself"¶
- Refactor your week-13 crawler to use
TaskGroup(oranyiotask group). - Add a "first-error wins" mode: as soon as any task raises, all siblings are cancelled and the group raises an
ExceptionGroup. - Add a "best-effort" mode: collect all results and exceptions, return both.
- Verify via test that cancelling the parent cancels every in-flight HTTP request within 100ms.
14.4 Idiomatic & Linter Drill¶
- Add
ruffRUF006(asyncio dangling tasks). Refactor anycreate_tasknot held in aTaskGroupor kept-reference set.
14.5 Production Hardening Slice¶
- Add OpenTelemetry instrumentation. Verify trace context propagates across
TaskGroupboundaries.