异步生成器的核心价值是避免i/o阻塞事件循环,仅适用于网络、磁盘、数据库等i/o密集场景;同步生成器适合内存受限但i/o不重的场景;纯计算任务用异步生成器反而更慢。

异步生成器不是“快一点的生成器”,而是解决I/O阻塞的流式开关
异步生成器(async def + yield)的核心价值不在节省CPU,而在避免I/O等待时阻塞整个事件循环。如果你用同步生成器读大文件、查数据库或调用HTTP接口,哪怕数据是“按需吐出”的,每次yield前的阻塞操作仍会让其他协程干等——这和普通函数没区别。
真正起作用的是:异步生成器内部能用await挂起自己,把控制权交还给事件循环,让别的任务继续跑。所以它只在涉及网络、磁盘、数据库等I/O场景下才有意义。
- 同步生成器适合:内存受限但I/O不重的场景,比如从本地CSV逐行解析
- 异步生成器适合:需要并发处理多个I/O密集型数据源,比如同时拉10个API、边读边写远程S3文件、实时消费Kafka消息流
- 别用异步生成器处理纯计算:比如
yield i**2 for i in range(10**6)——加async反而引入调度开销,更慢
用aiofiles + 异步生成器分块读大文件,避开MemoryError
直接open().read()或file.readlines()会把整个文件加载进内存;同步for line in file虽不爆内存,但阻塞事件循环。正确做法是用aiofiles配合固定大小分块读取:
import aiofiles
import asyncio
async def file_chunk_generator(filepath: str, chunk_size: int = 8192):
async with aiofiles.open(filepath, "rb") as f:
while True:
chunk = await f.read(chunk_size)
if not chunk:
break
yield chunk
-
chunk_size建议设为 4KB–64KB:太小导致频繁系统调用;太大失去流式意义 - 别用
aiofiles读超小文件(open更快,异步有初始化成本 - 如果要按行处理,不要在生成器里
chunk.decode().splitlines()——编码错误或跨块断行会出错;应在消费端组装缓冲区
StreamingResponse和EventSourceResponse选哪个?看客户端是否支持SSE
FastAPI中流式响应必须明确指定response_class,否则框架会尝试把整个异步生成器结果转成JSON或字符串,直接崩溃。
- 用
StreamingResponse:通用场景,客户端用fetch().body.getReader()或axios的responseType: 'stream'接收原始字节流,适合文件下载、日志流、二进制传输 - 用
EventSourceResponse:仅限浏览器客户端,自动处理text/event-stream协议,支持自动重连、事件类型标记(event: update),但不兼容curl或Postman默认行为 - 常见错误:
TypeError: object async_generator can't be used in 'await' expression——说明你忘了包一层StreamingResponse(...),而是直接return async_gen()
消费异步生成器时,async for是唯一安全方式
不能用next()、list()或itertools.islice()去操作异步生成器,它们只认同步迭代器。强行调用会抛TypeError: 'async_generator' object is not iterable。
- 正确消费姿势只有
async for item in async_gen() - 想提前终止?用
asyncio.shield()包裹并捕获GeneratorExit或CancelledError,但注意:异步生成器的close()方法不触发__aexit__,资源清理得靠async with上下文管理 - 调试时别用
print()打点:高并发下日志可能乱序;改用logging.info()并加extra={"coro_id": id(asyncio.current_task())}标定来源
aiohttp.ClientSession发起请求,session必须在生成器外部创建并传入,且要在所有yield完成后显式.close()——否则TCP连接泄露,跑几天就Too many open files。Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











