【问题标题】:outer async context manager finalized before inner async generator外部异步上下文管理器在内部异步生成器之前完成
【发布时间】:2021-05-26 16:26:39
【问题描述】:

给出以下最小示例:

@asynccontextmanager
async def async_context():
    try:
        yield
    finally:
        await asyncio.sleep(1)
        print('finalize context')


async def async_gen():
    try:
        yield
    finally:
        await asyncio.sleep(2)
        # will never be called if timeout is larger than in async_context
        print('finalize gen')


async def main():
    async with async_context():
        async for _ in async_gen():
            break


if __name__ == "__main__":
    asyncio.run(main())

我是 breaking 在迭代异步生成器时,我希望 finally 块在我的异步上下文管理器 finally 块运行之前完成。在这个例子中,"finalize gen" 永远不会被打印,因为程序在此之前就退出了。

请注意,我故意在生成器 finally 块中选择了 2 的超时,以便上下文管理器 finally 有机会在之前运行。如果我为两个超时都选择了1,则会打印两条消息。

这是一种竞争条件吗?我希望所有 finally 块在程序完成之前完成。

如何防止上下文管理器 finally 块在生成器 finally 块完成之前运行?

对于上下文:

我使用playwright 来控制铬浏览器。外部上下文管理器提供了一个在 finally 块中关闭的页面。

我正在使用 python 3.9.0

试试这个例子:https://repl.it/@trixn86/AsyncGeneratorRaceCondition

【问题讨论】:

    标签: python python-asyncio playwright-python


    【解决方案1】:

    异步上下文管理器对异步生成器一无所知。实际上,main 中的任何内容都不知道在您 break 之后的异步生成器。您已经让自己无法等待生成器完成。

    如果要等待生成器关闭,则需要显式处理关闭:

    async def main():
        async with async_context():
            gen = async_gen()
            try:
                async for _ in gen:
                    break
            finally:
                await gen.aclose()
    

    在 Python 3.10 中,您将能够使用 contextlib.aclosing 而不是 try/finally:

    async def main():
        async with async_context():
            gen = async_gen()
            async with contextlib.aclosing(gen):
                async for _ in gen:
                    break
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-09-22
      • 1970-01-01
      • 1970-01-01
      • 2016-07-16
      • 1970-01-01
      • 2019-01-03
      • 1970-01-01
      • 2017-03-17
      相关资源
      最近更新 更多