Выход из асинхронности Python для

#python #python-asyncio

Вопрос:

У меня есть ситуация, когда я хочу выйти из цикла async for. Мне удалось свести проблему к приведенному ниже приложению. Я ожидаю, что войду в раздел «наконец» контекстного менеджера при выходе из цикла в main. Другими словами, ожидаемый результат таков

пробовать

456

наконец

Выполнено

но что я получаю, так это

пробовать

456

Выполнено

наконец

а затем исключение, когда приложение закрывается.

Вот код

 import asyncio
from contextlib import asynccontextmanager

@asynccontextmanager
async def receiving():
    try:
        print('try')
        yield 123
    finally:
        print('finally')

async def request_all():
    async with receiving():
        yield 456

async def main():
    async for r in request_all():
        print(r)
        break
    print('done')

asyncio.run(main())
 

Я нашел это сообщение об ошибке, которое кажется похожим, но, насколько я могу судить, оно было устранено до 3.8. Я проверил свою проблему на 3.8.2 и 3.9.6

Ответ №1:

Я вижу, как такое поведение сбивает с толку, но я не думаю, что это ошибка. Если вы обходите исчерпание асинхронного итератора с break помощью , вы никогда не оставляете async with вход request_all , поэтому finally -блок не будет выполнен до тех пор, пока цикл событий не завершится. Это имеет то преимущество, что вы можете исчерпать генератор позже.

Если вы уверены, что вам больше не нужен генератор, вы можете закрыть async_generator вместо break ing, чтобы получить ожидаемое поведение:

 import asyncio
from contextlib import asynccontextmanager

@asynccontextmanager
async def receiving():
    try:
        print('try')
        yield 123
    finally:
        print('finally')

async def request_all():
    async with receiving():
        yield 456

async def main():
    gen = request_all()
    async for r in gen:
        print(r)
        await gen.aclose() #instead of break
    print('done')

asyncio.run(main())

 

Комментарии:

1. Спасибо! Это не очень интуитивно понятно, если вы спросите меня, но, по крайней мере, это не ошибка. Решение работает очень хорошо

2. Отказ от ответственности: Этот код предназначен для демонстрации того, как работает контекстный менеджер. Пожалуйста, не используйте его в производственном коде как есть 🙂

3. Просто слово предостережения: помните , что, кроме как с break , gen.aclose() не выйдет из for цикла-сразу, и код после этого будет выполнен на последней итерации.