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.
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.
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.
The main difference between them is that while Async/await has a specified return value, Coroutines leans more towards updating existing data.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With