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 提供更彻底的保证,适合对可靠性要求极高的场景
- 选择方案时优先考虑生态兼容性,再考虑并发模型的纯粹性