Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Iterating over asyncio.coroutine

I've been mucking around with asyncio recently, and while I'm beginning to get an intuition for how it works, there's something that I've not been able to do. I'm not sure if it's because I've got the construction wrong, or if there's a reason why what I'm trying to do doesn't make sense.

In short, I want to be able to iterate over a yielding asyncio.coroutine. For example, I'd like to be able to do something like:

@asyncio.coroutine
def countdown(n):
    while n > 0:
        yield from asyncio.sleep(1)
        n = n - 1
        yield n

@asyncio.coroutine
def do_work():
    for n in countdown(5):
        print(n)

loop.run_until_complete(do_work())

However, this throws an exception from the bowels of asyncio. I've tried other things, like for n in (yield from countdown(5)): ... but that also gives a similarly opaque runtime exception.

I can't immediately see why you shouldn't be do something like this, but I'm getting to the limits of my ability to understand what's going on.

So:

  • if it is possible to do this, how can I do it?
  • if it is not possible, why not?

Let me know if this question's not clear!

like image 670
user2463201 Avatar asked May 17 '14 09:05

user2463201


People also ask

Is coroutine deprecated?

coroutine" decorator is deprecated since Python 3.8, use "async def" instead · Javascript Required. Kindly enable Javascript. Updates · Content Removed.

What is Asyncio Get_event_loop ()?

asyncio. get_event_loop() Get the current event loop. If there is no current event loop set in the current OS thread, the OS thread is main, and set_event_loop() has not yet been called, asyncio will create a new event loop and set it as the current one.

How do I start Asyncio event loop?

The alternative way of starting up your event loop is to call the run_forever() method which will subsequently start your asyncio based event loop and have it run indefinitely until the program comes to an end or the stop() method is called.


1 Answers

In asyncio coroutines you should to use yield from and never yield. That's by design. Argument for yield from should be another coroutine or asyncio.Future instance only.

Calls of coroutine itself should be used with yield from again like yield from countdown(5).

For your case I recommend using queues:

import asyncio

@asyncio.coroutine
def countdown(n, queue):
    while n > 0:
        yield from asyncio.sleep(1)
        n = n - 1
        yield from queue.put(n)
    yield from queue.put(None)

@asyncio.coroutine
def do_work():
    queue = asyncio.Queue()
    asyncio.async(countdown(5, queue))
    while True:
        v = yield from queue.get()
        if v:
            print(v)
        else:
            break

asyncio.get_event_loop().run_until_complete(do_work())

Well, you can use check for values yielded by countdown, the following example works. But I think it is antipattern:

  1. Too easy to make a mess

  2. You anyway cannot compose countdown calls with, say, itertools functions. I mean something like sum(countdown(5)) or itertools.accumulate(countdown(5)).

Anyway, example with mixing yield and yield from in coroutine:

import asyncio

@asyncio.coroutine
def countdown(n):
    while n > 0:
        yield from asyncio.sleep(1)
        n = n - 1
        yield n

@asyncio.coroutine
def do_work():
    for n in countdown(5):
        if isinstance(n, asyncio.Future):
            yield from n
        else:
            print(n)

asyncio.get_event_loop().run_until_complete(do_work())
like image 198
Andrew Svetlov Avatar answered Oct 24 '22 08:10

Andrew Svetlov