外部异步上下文管理器在内部异步生成器之前完成

outer async context manager finalized before inner async generator

给定以下最小示例:

@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 块 运行s 之前完成。在这个例子中 "finalize gen" 将永远不会被打印,因为程序在此之前退出。

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

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

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

对于上下文:

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

我正在使用 python 3.9.0.

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

异步上下文管理器对异步生成器一无所知。事实上,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