Generator based coroutines have a send()
method which allow bidirectional communication between the caller and the callee and resumes a yielded generator coroutine from the caller. This is the functionality that turns generators into coroutines.
While the new native async/await
coroutines provide superior support for async I/O, I do not see how to get the equivalent of send()
with them. The use of yield
in async
functions is explicitly forbidden, so native coroutines can return only once using a return
statement. Although await
expressions bring new values into a coroutine, those values come from callees, not the caller, and the awaited call is evaluated from the beginning each time, not from where it left off.
Is there a way to resume a returned coroutine from where it left off and potentially send in a new value?
How can I emulate the techniques in David Beazley's Curious Course on Coroutines and Concurrency using native coroutines?
The general code pattern I have in mind is something like
def myCoroutine():
...
while True:
...
ping = yield(pong)
...
and in the caller
while True:
...
buzz = myCoroutineGen.send(bizz)
...
Edit
I accepted Kevin's answer but I have noticed that the PEP says
Coroutines are based on generators internally, thus they share the implementation. Similarly to generator objects, coroutines have throw() , send() and close() methods.
...
throw() , send() methods for coroutines are used to push values and raise errors into Future-like objects.
So apparently native coroutines do have a send()
? How does it work without yield
expression to receive the values inside the coroutine?
Is there a way to resume a returned coroutine from where it left off and potentially send in a new value?
No.
async
and await
are just syntactic sugar for yield from
. When a coroutine returns (with the return
statement), that's it. The frame is gone. It is not resumable. This is exactly how generators have always worked. For example:
def foo():
return (yield)
You can do f = foo(); next(f); f.send(5)
, and you will get back 5. But if you try to f.send()
again, it does not work, because you already returned from the frame. f
is no longer a live generator.
Now, as for new coroutines, so far as I can tell, it seems yielding and sending is reserved for communication between the event loop and certain basic predicates such as asyncio.sleep()
. The coroutines yield asyncio.Future
objects up to the event loop, and the event loop sends those same future objects back into the coroutine once the associated operations have been completed (they are typically scheduled via call_soon()
and the other event loop methods).
You can yield future objects by awaiting them, but it's not a general-purpose interface like .send()
was. It is specifically intended for use by the event loop implementation. If you are not implementing an event loop, you probably do not want to be playing around with this. If you are implementing an event loop, you need to ask yourself why the perfectly good implementations in asyncio
are not sufficient for your purposes and explain what specifically you are trying to do before we can help you.
Please note that yield from
is not deprecated. If you want coroutines that are not tied to an event loop at all, just use that instead. async
and await
are specifically designed for asynchronous programming with event loops. If that is not what you are doing, then async
and await
are the wrong tool to begin with.
One more thing:
The use of yield
in async functions is explicitly forbidden, so native coroutines can return only once using a return
statement.
await
expressions do yield control. await something()
is entirely analogous to yield from something()
. They just changed the name so it would be more intuitive to people not familiar with generators.
For those of you who actually are interested in implementing your own event loop, here's some example code showing a (very minimal) implementation. This event loop is extremely stripped down, because it is designed to run certain specially-written coroutines synchronously as if they were normal functions. It does not provide the full range of support you would expect from a real BaseEventLoop implementation, and is not safe for use with arbitrary coroutines.
Ordinarily, I would include the code in my answer, rather than linking to it, but there are copyright concerns and it is not critical to the answer itself.