Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python coroutines

I have a little bit of experience with promises in Javascript. I am quite experienced with Python, but new to its coroutines, and there is a bit that I just fail to understand: where does the asynchronicity kick in?

Let's consider the following minimal example:

async def gen():
    await something
    return 42

As I understand it, await something puts execution of our function aside and lets the main program run other bits. At some point something has a new result and gen will have a result soon after.

If gen and something are coroutines, then by all internet wisdom they are generators. And the only way to know when a generator has a new item available, afaik, is by polling it: x=gen(); next(x). But this is blocking! How does the scheduler "know" when x has a result? The answer can't be "when something has a result" because something must be a generator, too (for it is a coroutine). And this argument applies recursively.

I can't get past this idea that at some point the process will just have to sit and wait synchronously.

like image 969
Paul Avatar asked Mar 06 '17 10:03

Paul


People also ask

What are coroutines in Python?

Coroutines are generalizations of subroutines. They are used for cooperative multitasking where a process voluntarily yield (give away) control periodically or when idle in order to enable multiple applications to be run simultaneously.

What are coroutines used for?

A coroutine is a concurrency design pattern that you can use on Android to simplify code that executes asynchronously. Coroutines were added to Kotlin in version 1.3 and are based on established concepts from other languages.

Is async await better than coroutine?

The main difference between them is that while Async/await has a specified return value, Coroutines leans more towards updating existing data.

Can Python be async?

Since Python 3.5, it is possible to use asynchronism in your scripts. This evolution allowed the use of new keywords async and await along with the new module asyncio. Async and Await were firstly introduced in C#, in order to structure non-blocking code in a similar fashion as you would write blocking code.


1 Answers

The secret sauce here is the asyncio module. Your something object has to be an awaitable object itself, and either depend on more awaitable objects, or must yield from a Future object.

For example, the asyncio.sleep() coroutine yields a Future:

@coroutine
def sleep(delay, result=None, *, loop=None):
    """Coroutine that completes after a given time (in seconds)."""
    if delay == 0:
        yield
        return result

    if loop is None:
        loop = events.get_event_loop()
    future = loop.create_future()
    h = future._loop.call_later(delay,
                                futures._set_result_unless_cancelled,
                                future, result)
    try:
        return (yield from future)
    finally:
        h.cancel()

(The syntax here uses the older generator syntax, to remain backwards compatible with older Python 3 releases).

Note that a future doesn't use await or yield from; they simply use yield self until some condition is met. In the above async.sleep() coroutine, that condition is met when a result has been produced (in the async.sleep() code above, via the futures._set_result_unless_cancelled() function called after a delay).

An event loop then keeps pulling in the next 'result' from each pending future it manages (polling them efficiently) until the future signals it is done (by raising a StopIteration exception holding the results; return from a co-routine would do that, for example). At that point the coroutine that yielded the future can be signalled to continue (either by sending the future result, or by throwing an exception if the future raised anything other than StopIteration).

So for your example, the loop will kick off your gen() coroutine, and await something then (directly or indirectly) yields a future. That future is polled until it raises StopIteration (signalling it is done) or raises some other exception. If the future is done, coroutine.send(result) is executed, allowing it to then advance to the return 42 line, triggering a new StopIteration exception with that value, allowing a calling coroutine awaiting on gen() to continue, etc.

like image 57
Martijn Pieters Avatar answered Oct 10 '22 13:10

Martijn Pieters