--- title: "Python Async: Beyond the Basics" date: 2026-05-12T11:00:00+08:00 draft: false description: "Understanding the event loop, writing proper async context managers, and avoiding the most common pitfalls with asyncio.gather and cancel scopes." tags: - Python - Async - Programming --- Async Python is no longer optional if you're doing any kind of I/O-bound work. Beyond the basics of `async`/`await`, understanding the event loop helps debug elusive issues. ## The Event Loop in Practice The event loop runs one coroutine at a time, switching at `await` points. This is cooperative multitasking — a coroutine that never awaits blocks everything else. If you have CPU-bound work inside an async function, offload it to a thread pool with `asyncio.to_thread()`. ```python import asyncio # Bad: blocks the event loop async def bad(): result = heavy_computation() # CPU-bound, no await return result # Good: offloads to a thread async def good(): result = await asyncio.to_thread(heavy_computation) return result ``` ## Async Context Managers Writing proper async context managers is essential for managing connections and locks: ```python class AsyncConnection: async def __aenter__(self): self.conn = await create_connection() return self.conn async def __aexit__(self, *args): await self.conn.close() ``` The `contextlib.asynccontextmanager` decorator makes this even cleaner for simple cases. ## Common Pitfalls with `asyncio.gather` `gather` runs tasks concurrently, but one failing task doesn't cancel the others by default. If you need all-or-nothing semantics, use `asyncio.TaskGroup` (Python 3.11+): ```python async with asyncio.TaskGroup() as tg: t1 = tg.create_task(fetch(url1)) t2 = tg.create_task(fetch(url2)) # Both succeeded, or both were cancelled ``` ## Cancel Scopes Cancellation in asyncio is cooperative: `asyncio.CancelledError` is raised at the next `await`. If your coroutine catches and suppresses it, the task becomes uncancellable — usually a bug. Use `asyncio.shield()` sparingly to protect critical cleanup code. ## Structured Concurrency The trend across languages (Trio in Python, Kotlin coroutines, Swift concurrency) is toward structured concurrency: every task has a clear parent scope that determines its lifetime. `TaskGroup` is Python's step in this direction. Embrace it over bare `create_task()` calls. Async Python has matured significantly. Most of the old footguns have been addressed, but the fundamentals — understand the event loop, respect cancellation, and structure your concurrency — remain essential.