Что происходит с асинхронными итераторами без объединения?
Скажи, у меня есть следующая функция
async def f1():
async for item in asynciterator():
return
Что происходит с асинхронным итератором после
await f1()
? Должен ли я беспокоиться об уборке или генератор будет каким-то образом собирать мусор, когда он исчезает из поля зрения?
1 ответ
Должен ли я беспокоиться об уборке или генератор будет каким-то образом собирать мусор, когда он исчезает из поля зрения?
TL; DR Python gc и asyncio обеспечит возможную очистку не полностью повторных асинхронных генераторов.
"Очистка" здесь относится к запуску кода, указанного finally
вокруг yield
или __aexit__
часть диспетчера контекста, используемого в with
заявление по всему yield
, Например, print
в этом простом генераторе вызывается тем же механизмом, который используется aiohttp.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 регистрирует обработчик финализатора asyncgen, но никогда не получает возможности выполнить, потому что мы используем run_until_complete
который останавливает цикл сразу после выполнения сопрограммы.
Если бы мы попытались еще раз запустить тот же цикл обработки событий, мы бы увидели, что очистка выполнена:
>>> asyncio.get_event_loop().run_until_complete(asyncio.sleep(0))
cleaned up
В обычном асинхронном приложении это не приводит к проблемам, поскольку цикл обработки событий обычно выполняется столько же времени, сколько приложение. Если нет цикла событий для очистки асинхронных генераторов, это, вероятно, означает, что процесс все равно завершается.