必须用 asyncgen.aclose() 替代 generator.close():普通生成器的 close() 是同步方法,遇 await 抛 RuntimeError;异步生成器(async def)支持 async finally 和 aclose(),可安全 await 清理逻辑。

generator.close() 不会等待 async cleanup
Python 的 generator.close() 是同步方法,遇到 await 就直接抛 RuntimeError: asynchronous generator ignored GeneratorExit。这不是 bug,是语言设计限制:GeneratorExit 不能被协程暂停,所以任何 await 都会中断清理流程。
用 asyncgen.aclose() 替代 generator.close()
必须把普通生成器换成异步生成器(async def 定义),才能用 aclose() —— 它是协程,可安全 await 清理逻辑:
async def resource_generator():
res = await acquire_resource()
try:
yield res
finally:
await release_resource(res) # ✅ 这里能 await
正确关闭方式:
agen = resource_generator()
... 使用后
await agen.aclose() # ✅ 触发 finally 中的 await
-
aclose()是异步生成器(asyncgen)的原生方法,和__aexit__同级语义 - 普通
generator没有aclose(),调用会报AttributeError - 若已用普通生成器封装了异步资源,无法靠 monkey patch 补救,必须重构为
async def
try/finally 里不能写 await,但 asyncgen 支持 async finally
异步生成器的 finally 块允许 await,这是关键差异:
async def bad_example():
yield 1
# ❌ 这里不是 finally,且 close() 不会执行到这
await cleanup() # 永远不会运行
async def good_example():
try:
yield 2
finally:
await cleanup() # ✅ aclose() 会进入并 await
- 普通生成器的
finally是同步上下文,写await会语法报错 - 异步生成器的
finally是异步上下文,支持await,且aclose()必走该路径 - 不要依赖
__del__或弱引用回调做异步清理——时机不可控、可能在事件循环关闭后触发
兼容 sync/async 调用场景时需显式分支
如果函数既要支持同步迭代(for x in gen:),又要支持异步迭代(async for x in agen:),不能混用同一对象。常见做法是提供两个工厂函数:
def sync_resource_gen():
res = sync_acquire()
try:
yield res
finally:
sync_release(res)
async def async_resource_gen():
res = await async_acquire()
try:
yield res
finally:
await async_release(res)
- 不要试图让一个函数同时返回
Generator和AsyncGenerator—— 类型系统和运行时都不支持 - 若上层逻辑不确定调用方式,建议统一走异步路径(即强制用
async for+aclose()),避免清理遗漏 - 第三方库(如
httpx的stream)通常只提供异步接口,就是出于这个原因
异步清理真正可靠的入口只有 aclose() + 异步生成器的 async finally,其余所有“绕过”方案都会在某个边界条件下漏掉 await。










