внешний асинхронный менеджер контекста завершен до внутреннего асинхронного генератора

Учитывая следующий минимальный пример:

      @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 во время итерации по генератору async, и я хочу, чтобы блок finally завершился до того, как будет запущен блок менеджера контекста async. В этом примере "finalize gen" никогда не будет напечатан, потому что программа завершится раньше, чем это произойдет.

Обратите внимание, что я намеренно выбрал тайм-аут 2в блоке генераторов, чтобы у менеджеров контекста была возможность запускаться раньше. Если бы я выбрал 1 для обоих таймаутов будут напечатаны оба сообщения.

Это своего рода состояние гонки? Я ожидал, что все блоки будут завершены до завершения программы.

Как я могу предотвратить запуск блока контекстных менеджеров до завершения работы блока генераторов?

Для контекста:

Я использую драматурга для управления браузером хрома. Внешний диспетчер контекста предоставляет страницу, которую он закрывает в finally блокировать.

Я использую питон 3.9.0.

Попробуйте этот пример: https://repl.it/@trixn86/AsyncGeneratorRaceCondition

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 вместо попытки / наконец:

      async def main():
    async with async_context():
        gen = async_gen()
        async with contextlib.aclosing(gen):
            async for _ in gen:
                break
Другие вопросы по тегам