跳至内容

Python Async: Beyond the Basics

2026-05-12

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().

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:

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+):

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.