async/await 与生成器协程(如 yield)的本质区别主要体现在以下几个方面:
1. 设计目标与语义
-
async/await
是专为异步编程设计的语法糖,明确标识协程的异步行为。async def
定义的函数是原生协程(Native Coroutine),其类型为coroutine
,与生成器彻底解耦。语义上更清晰,强调非阻塞 I/O 和事件循环协作。 -
生成器协程
基于生成器(Generator)的yield
或yield from
实现协程,本质上是生成器的扩展。其类型为generator
,最初设计用于惰性迭代,后被改造为协程。语义上存在歧义(既是迭代器又是协程)。
2. 底层机制
-
async/await
通过await
挂起协程时,直接与事件循环交互,无需生成器的栈帧管理。底层由types.coroutine
或async def
直接生成协程对象,运行时效率更高,且能避免生成器的冗余状态保存。 -
生成器协程
依赖生成器的暂停/恢复机制,每次yield
会保存当前栈帧状态,并通过send()
或throw()
传递值/异常。需要装饰器(如@asyncio.coroutine
)标记为协程,本质仍是生成器包装。
3. 功能与兼容性
-
async/await
- 支持
async with
(异步上下文管理器)、async for
(异步迭代器)。 - 禁止混用
yield
,语法检查更严格。 - 与 Future 和 Task 无缝集成,直接通过
await
等待其他协程或异步对象。
- 支持
-
生成器协程
- 无法使用
async with
或async for
。 - 允许在协程中混用
yield
(但可能导致逻辑混乱)。 - 需通过
yield from
委托子生成器,依赖显式的事件循环调度。
- 无法使用
4. 错误处理
-
async/await
异常直接通过协程调用链传播,与同步代码的try/except
行为一致。await
会自动捕获被等待对象的异常。 -
生成器协程
异常需通过generator.throw()
手动注入到生成器内部,错误处理逻辑更复杂,容易遗漏栈追踪信息。
5. 性能与未来兼容性
-
async/await
运行时优化更好(如 PEP 492 引入的协程快速路径),且是 Python 异步生态的未来方向。生成器协程已在 Python 3.10 被标记为弃用(@asyncio.coroutine
)。 -
生成器协程
存在历史包袱,性能略低(需维护生成器状态),且逐渐被官方废弃。
总结表
特性 | async/await | 生成器协程(yield) |
---|---|---|
类型 | coroutine | generator |
语法明确性 | 专用关键字,无歧义 | 依赖 yield ,语义双重性 |
异步原语支持 | 完整(async with/for) | 不支持 |
错误传播 | 自动传播 | 需手动 throw() |
事件循环交互 | 直接通过 await | 需 yield from 或显式 yield |
性能 | 更高效 | 稍低(生成器开销) |
未来兼容性 | 官方推荐,持续维护 | 已弃用 |
本质区别
async/await 是语言层面对协程的一等公民支持,解耦了协程与生成器,提供了更高效、更符合直觉的异步编程模型;而生成器协程是通过复用生成器机制实现的临时方案,存在语义混杂和性能局限。