
本文澄清异步生成器(async def ... yield)的常见误解:它不自动并发执行循环体,而是按序逐次 await 每次迭代,因此 async for 本身是串行的;如需并发,需显式创建任务并协同调度。
本文澄清异步生成器(`async def ... yield`)的常见误解:它不自动并发执行循环体,而是按序逐次 `await` 每次迭代,因此 `async for` 本身是串行的;如需并发,需显式创建任务并协同调度。
异步生成器(async generator)是 Python 3.6+ 中支持 async for 语法的关键特性,常被初学者误认为“天然并发”——即认为 for i in range(5) 中的每次循环体都会立即启动、并行等待 await。但事实恰恰相反:async for 是协程驱动的同步迭代协议,每次 yield 前的代码块(包括其中所有 await 表达式)必须完全执行完毕,才会进行下一次迭代。
在你的原始示例中:
async def async_gen():
for i in range(5):
print(f"entering iteration {i}")
await asyncio.sleep(3) # ⚠️ 此处阻塞整个迭代流程
print(f"ending iteration {i}")
yield iawait asyncio.sleep(3) 并非“发起一个后台睡眠”,而是让当前协程在此处挂起 3 秒,控制权交还事件循环;但因 async for value in async_gen() 是单任务驱动,事件循环在恢复该协程后,才继续执行 print(f"ending iteration {i}") 和 yield i,随后才进入下一轮 for 循环。因此整个流程严格串行,总耗时为 5 × 3s = 15s。
✅ 正确实现并发的核心思路是:将耗时操作(如 I/O、网络请求、数据库查询)拆分为独立协程,并通过 asyncio.create_task() 并发调度,而非依赖生成器循环本身。
立即学习“Python免费学习笔记(深入)”;
以下为推荐重构方案:
import asyncio
async def fetch_item(i: int) -> int:
"""模拟一次远程数据库查询:先准备,再等待,最后返回"""
print(f"→ entering iteration {i}")
await asyncio.sleep(3) # 模拟网络延迟或 DB 查询耗时
print(f"← ending iteration {i}")
return i
async def async_gen():
# 仅负责数据序列定义,不包含任何 await 或 I/O
for i in range(5):
yield i
async def main():
# 步骤 1:收集所有待执行的任务
tasks = []
async for value in async_gen():
task = asyncio.create_task(fetch_item(value)) # ? 立即启动并发任务
tasks.append(task)
# 步骤 2:等待全部任务完成(保持结果顺序可选)
results = await asyncio.gather(*tasks)
for res in results:
print(res)
if __name__ == "__main__":
asyncio.run(main())运行此代码,输出将接近你预期的并发行为(注意:print 顺序可能因任务调度微小差异略有交错,但所有 "→ entering" 几乎同时出现,约 3 秒后所有 "← ending" 集中输出):
→ entering iteration 0 → entering iteration 1 → entering iteration 2 → entering iteration 3 → entering iteration 4 ← ending iteration 0 ← ending iteration 1 ← ending iteration 2 ← ending iteration 3 ← ending iteration 4 0 1 2 3 4
⏱ 总耗时稳定在约 3 秒(以最慢任务为准),真正实现了 I/O 密集型操作的并发加速。
⚠️ 关键注意事项:
- 异步生成器 ≠ 并发生成器:它只是让 yield 可以出现在 await 之后,用于流式生成异步数据(如分页 API 响应),但迭代逻辑仍是线性的;
- asyncio.create_task() 是启用并发的最小安全单元,避免直接使用 ensure_future()(除非明确需要其底层语义);
- 若需保持 yield 顺序与消费顺序严格一致(例如流式处理不可乱序),请勿在 async for 中并发启动任务;此时应考虑 asyncio.Semaphore 限流或改用 asyncio.Queue 构建生产者-消费者模型;
- 所有并发任务共享同一事件循环,确保无阻塞调用(如 time.sleep() 或 CPU 密集型计算)混入 fetch_item,否则会拖垮整个并发性能。
总结:异步生成器的价值在于解耦数据序列定义与异步获取逻辑,而非提供自动并行。真正的并发需由开发者主动设计任务拓扑——这是 asyncio “显式优于隐式”哲学的典型体现。


















