联合异步迭代器会发生什么?

what happens to uniterated async iterators?

假设我有以下功能

async def f1():
    async for item in asynciterator():
        return

异步迭代器在

之后发生了什么
await f1()

?我应该担心清理还是发电机在看不见时会以某种方式被垃圾收集?

Should I worry about cleaning up or will the generator be somehow garbage collected when it goes out of sight?

TL;DR Python 的 gc 和 asyncio 将确保最终清理未完全迭代的异步生成器。

这里的

"Cleanup"是指运行在yield周围的finally或者上下文管理器的__aexit__部分指定的代码在 yield 周围的 with 语句中使用。例如,这个简单生成器中的 printaiohttp.ClientSession 用于关闭其资源的相同机制调用:

async def my_gen():
    try:
        yield 1
        yield 2
        yield 3
    finally:
        await asyncio.sleep(0.1)  # make it interesting by awaiting
        print('cleaned up')

如果你运行一个遍历整个生成器的协程,清理将立即执行:

>>> async def test():
...     gen = my_gen()
...     async for _ in gen:
...         pass
...     print('test done')
... 
>>> asyncio.get_event_loop().run_until_complete(test())
cleaned up
test done

注意清理是如何在循环后立即执行的,即使生成器仍在范围内而没有机会收集垃圾。这是因为 async for 循环确保异步生成器在循环耗尽时进行清理。

问题是当循环没有耗尽时会发生什么:

>>> async def test():
...     gen = my_gen()
...     async for _ in gen:
...         break  # exit at once
...     print('test done')
... 
>>> asyncio.get_event_loop().run_until_complete(test())
test done

此处 gen 超出了范围,但根本没有进行清理。如果你用一个普通的生成器尝试这个,清理会被引用立即调用(尽管在 test 退出 之后仍然是 ,因为那是 运行ning 发电机不再被提及),这是可能的,因为 gen 不参与循环:

>>> def my_gen():
...     try:
...         yield 1
...         yield 2
...         yield 3
...     finally:
...         print('cleaned up')
... 
>>> def test():
...     gen = my_gen()
...     for _ in gen:
...         break
...     print('test done')
... 
>>> test()
test done
cleaned up

由于 my_gen 是一个 异步 生成器,它的清理也是异步的。这意味着它不能仅由垃圾收集器执行,它需要由事件循环 运行 执行。为了使这成为可能,asyncio registers asyncgen 终结器挂钩,但它永远没有机会执行,因为我们使用的是 run_until_complete,它会在执行协程后立即停止循环。

如果我们尝试更多地旋转相同的事件循环,我们会看到执行的清理:

>>> asyncio.get_event_loop().run_until_complete(asyncio.sleep(0))
cleaned up

在普通的 asyncio 应用程序中,这不会导致问题,因为事件循环通常 运行s 与应用程序一样长。如果没有事件循环来清理异步生成器,则可能意味着进程正在退出。