【问题标题】:what happens to uniterated async iterators?联合异步迭代器会发生什么?
【发布时间】:2019-04-12 02:28:42
【问题描述】:

假设我有以下功能

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

之后异步迭代器会发生什么

await f1()

?我应该担心清理工作吗,或者生成器在看不见时会以某种方式被垃圾收集吗?

【问题讨论】:

  • 猜测:f1() 返回协程,它只是堆上的一个可调用对象,包括函数的帧(局部变量等)。因此,垃圾收集应该清理它就好了。在这种情况下,您不希望 f1 保留任何外部资源,例如文件句柄
  • 异步器怎么样。在异步器中,我使用 aiohttp 会话作为上下文管理器来执行获取。完成获取后,我解析正文并从正文中生成项目。我是否应该在收到 http 响应并开始从中解析和生成项目之前立即释放 aiohttp 会话?
  • 更准确地说,当异步生成器即将收集垃圾时,asyncio 会调度 agen.aclose() 协程。

标签: python iterator python-asyncio async-iterator


【解决方案1】:

我应该担心清理工作还是发电机在看不见时会被垃圾收集?

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

此处的“清理”指的是运行由finally 围绕yield 指定的代码,或由with 围绕yield 语句中使用的上下文管理器的__aexit__ 部分指定的代码。例如,这个简单生成器中的 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,因为那时正在运行的生成器不再被引用),这是可能的,因为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 应用程序中,这不会导致问题,因为事件循环通常与应用程序一样长时间运行。如果没有事件循环来清理异步生成器,则可能意味着进程无论如何都退出了。

【讨论】:

  • 如果你使用asyncio.run()而不是asyncio.run_until_complete(),它会在返回之前自动等待异步生成器清理完毕。
  • 还有PEP 533,它应该使异步生成器在确定的时间被清理,而不是在它们碰巧被垃圾收集时清理。不幸的是,由于我不知道的原因,该 PEP 目前被“推迟”了。
猜你喜欢
  • 2019-08-30
  • 1970-01-01
  • 2021-10-22
  • 2014-02-04
  • 1970-01-01
  • 2020-09-13
  • 1970-01-01
  • 2017-11-15
  • 1970-01-01
相关资源
最近更新 更多