2026-09-10 Python 设计

Python 异步编程:从回调地狱到结构化并发

梳理 asyncio 的演进脉络,对比 TaskGroup、Trio 等结构化并发方案,给出在 Web 爬虫和 API 网关中的实际选型建议。

异步编程的演进之路

Python 的异步编程经历了从回调、Future、async/await 到结构化并发的演进。每一步都在解决前一代方案的痛点。理解这个演进过程,才能在实际项目中做出正确的技术选型。

回调时代的问题

早期的异步编程依赖回调函数,代码很快陷入「回调地狱」:

# 回调地狱示例
def fetch_user(user_id, on_success, on_error):
    db.query("SELECT * FROM users WHERE id=?", [user_id],
        on_success=lambda user:
            fetch_orders(user.id,
                on_success=lambda orders:
                    fetch_details(orders[0].id,
                        on_success=lambda detail:
                            print(detail),
                        on_error=on_error),
                on_error=on_error),
        on_error=on_error)

这种代码难以阅读、难以调试,错误处理更是一场噩梦。

async/await 的救赎

Python 3.5 引入的 async/await 语法让异步代码看起来像同步代码:

async def fetch_user_data(user_id):
    try:
        user = await db.fetch_user(user_id)
        orders = await db.fetch_orders(user.id)
        detail = await db.fetch_details(orders[0].id)
        return detail
    except DatabaseError as e:
        logger.error(f"查询失败: {e}")
        raise

但这只是语法糖,并没有解决并发任务的生命周期管理问题。

结构化并发:TaskGroup

Python 3.11 引入的 asyncio.TaskGroup 是结构化并发的关键一步:

async def crawl_urls(urls):
    results = {}
    async with asyncio.TaskGroup() as tg:
        for url in urls:
            tg.create_task(fetch_and_store(url, results))
    # TaskGroup 退出时,所有子任务已完成或已取消
    return results

async def fetch_and_store(url, results):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as resp:
            results[url] = await resp.text()

TaskGroup 的核心优势:任何一个子任务抛出异常,其他子任务会被自动取消,不会出现「孤儿任务」。

Trio:更彻底的结构化并发

Trio 是一个第三方库,它从设计之初就贯彻了结构化并发的理念。它的 nursery 概念比 TaskGroup 更早也更纯粹:

import trio

async def parent():
    async with trio.open_nursery() as nursery:
        nursery.start_soon(child1)
        nursery.start_soon(child2)
        nursery.start_soon(child3)
    # 所有子任务在此处已全部完成

async def child1():
    await trio.sleep(1)
    print("child1 done")
Trio 的取消机制比 asyncio 更可靠。在 asyncio 中,CancelledError 可能被意外捕获;Trio 使用「取消作用域」确保取消信号不会被吞掉。

实际选型建议

场景 推荐方案 理由
Web 爬虫 asyncio + TaskGroup 生态成熟,aiohttp 支持好
API 网关 asyncio + TaskGroup FastAPI/Starlette 原生支持
高可靠性服务 Trio 取消机制更可靠,边界清晰
遗留项目迁移 asyncio.gather 兼容性最好,改动最小

总结

  • async/await 解决了回调地狱,但没有解决任务生命周期管理
  • TaskGroup(Python 3.11+)是标准库的结构化并发方案,优先使用
  • Trio 提供更彻底的保证,适合对可靠性要求极高的场景
  • 选择方案时优先考虑生态兼容性,再考虑并发模型的纯粹性