David Brochart
David Brochart

Reputation: 885

Equivalent to "yield" in Python's "async def" functions

I've heard that the @asyncio.coroutine version of coroutine will eventually be deprecated in Python (maybe after 3.8?), and that only async def will be supported. Today I use the decorator version in order to be able to just yield (not yield from another coroutine), because I'm waiting for some change (polling inside e.g. a while loop) or to split up big portions of time-consuming blocking code into smaller chunks (and thus improve the concurrency experience).

Here is a minimal example:

import asyncio

@asyncio.coroutine
def foo(l):
    print('entering foo')
    while l[0] == 0:
        yield
    print('leaving foo')

async def bar(l):
    print('entering bar')
    await asyncio.sleep(1)
    l[0] = 1
    await asyncio.sleep(1)
    print('leaving bar')

async def main():
    l = [0]
    t0 = asyncio.ensure_future(foo(l))
    t1 = asyncio.ensure_future(bar(l))
    await t0
    await t1

loop = asyncio.get_event_loop()
loop.run_until_complete(main())

This will print the following:

entering foo
entering bar
# foo is polling
# 1 second elapses
leaving foo
# 1 second elapses
leaving bar

How can we achieve that in an async def version of coroutine? Should we use await asyncio.sleep(0)?

Upvotes: 4

Views: 333

Answers (2)

Mikhail Gerasimov
Mikhail Gerasimov

Reputation: 39546

await asyncio.sleep(0) can be used to do what you want, but in current case it's not a good solution. Event loop would need to return into foo every "tick" of it's execution: it wastes CPU resources.

When you want to wait for something to happen inside a coroutine proper way is to use asyncio.Event:

async def foo(event):
    print('entering foo')
    await event.wait()
    print('leaving foo')

async def bar(event):
    print('entering bar')
    await asyncio.sleep(1)
    event.set()
    await asyncio.sleep(1)
    print('leaving bar')

async def main():
    event = asyncio.Event()
    t0 = asyncio.ensure_future(foo(event))
    t1 = asyncio.ensure_future(bar(event))
    await t0
    await t1

loop = asyncio.get_event_loop()
loop.run_until_complete(main())

This way event loop will return into foo only once: when event is actually has been set.

Upvotes: 4

David Brochart
David Brochart

Reputation: 885

Yes, I think that await asyncio.sleep(0) is the proper way to do it, see https://github.com/python/asyncio/issues/284. So foo becomes:

async def foo(l):
    print('entering foo')
    while l[0] == 0:
        await asyncio.sleep(0)
    print('leaving foo')

Upvotes: 1

Related Questions