12

According to this blog, normal generators will be immediately closed when there is no reference to it. (CPython exclusive though). My question is "Does this apply to async-generators that don't have 'await' in its finally-block?"

Motivation

I'm an auther of an async library, and want async-generators to work like the normal ones. Otherwise, the user have to code something like this

    agen = async_generator_function()
    async with async_closing(agen):
        async for v in agen:
            if some_condition:
                break
            do_something()

instead of this

    async for v in async_generator_function():
        if some_condition:
            break
        do_something()

Which is pretty annoying. Luckly its unit test always works as I expected so I want to know if it's guaranteed or not.

Investigation

The following code

import asyncio

async def agen_func():
    try:
        for i in range(10):
            yield i
    finally:
        print('finalized')

async def main():
    async for i in agen_func():
        print(i)
        if i > 2:
            break
    print('end of main()')

asyncio.run(main())

printed

0
1
2
3
end of main()
finalized

And the following code

import trio

async def agen_func():
    try:
        for i in range(10):
            yield i
    finally:
        print('finalized')

async def main():
    async for i in agen_func():
        print(i)
        if i > 2:
            break
    print('end of main()')

trio.run(main)

printed the same, which looks like both asyncio and trio say "NO" to my question, because if async-generators were closed immediately as I expected, they would have printed finalized before end of main(). Actually, if you use those two async libraries, the answer to my question probably is "NO".

But here is an interesting thing. If you don't use them, async-generators work as I expected.

async def agen_func():
    try:
        for i in range(10):
            yield i
    finally:
        print('finalized')


async def main():
    async for i in agen_func():
        print(i)
        if i > 2:
            break
    print('end of main()')


try:
    main().send(None)
except StopIteration:
    pass
0
1
2
3
finalized
end of main()

And this is how the async library I'm currently developing works.

set_asyncgen_hooks

According to PEP525, asyncio does some hacky stuff called sys.set_asyncgen_hooks(). trio does it too. I believe this is the reason why async-generators don't work as I expected under them. And it's understandable because it's necessary for the async-generators whose finally-block contains await.

more precise question

So my question is:

If you are using CPython, and the async-library you are using doesn't do the hacky stuff above, then async-generators that don't have await in its finally-block will be immediately closed when there is no reference to it?

environment

  • CPython 3.8.1
  • Trio 0.17.0
Nattōsai Mitō
  • 778
  • 4
  • 13

0 Answers0