Send data to asyncio coroutine

Ian Kelly ian.g.kelly at gmail.com
Tue Jul 28 19:06:23 EDT 2015


On Tue, Jul 28, 2015 at 1:17 PM, Javier <jcarmena at gmail.com> wrote:
> Hello again. I have been investigating a bit your example. I don't understand why I can't write something like this:
>
> --------
>
> import asyncio
>
> def foo():
>     print("start foo")
>     try:
>         while True:
>             val = yield
>             print("foo:", val)
>             yield from asyncio.sleep(3)
>     except GeneratorExit:
>         print("foo closed")
>     print("exit foo")
>
> def bar(next):
>     print("start bar")
>     next.send(None)
>     try:
>         while True:
>             val = yield
>             next.send("bar/"+val)
>     except GeneratorExit:
>         print("bar closed")
>     print("exit bar")
>
> def fun(next):
>     next.send(None)
>     for e in ["hello", "world", "I'm", "pythonist"]:
>         next.send(e)
>
> @asyncio.coroutine
> def run():
>     fun(bar(foo()))
>
> loop = asyncio.get_event_loop()
> loop.run_until_complete(run())
> loop.close()

Because "yield from asyncio.sleep(3)" doesn't magically pause the
coroutine as you want it to. It yields a future, which is meant to be
yielded back up the coroutine chain to the event loop. The event loop
would then resume the coroutine once the future is done, which in the
case of asyncio.sleep will happen after the sleep timer completes.

In your example, the future never makes it back to the event loop.
asyncio.sleep yields the future to foo, and since foo is suspended by
a yield from, foo yields the future to bar, where it is the result of
the next.send call. The return value of next.send is ignored, so the
future just gets dropped on the floor at this point. bar yields to
fun, which sends bar the next string in its list, "world". bar sends
"world" to foo, and since foo is still suspended by a yield from, it
sends "world" on to the asyncio.sleep future. Not a new asyncio.sleep
future, but the same one that it's still yielding from. The future
then realizes that something is wrong, because its generator code is
running again but it doesn't have a result yet, so it throws that
AssertionError.

If you want the yield from in foo to work properly, then you need to
make sure that the future gets back to the event loop, and if you do
that in some tricky way other than a yield from chain, you'll also
need to make sure that you're not trying to send it more data before
foo has resumed.

> I think this is a big flaw in python/asyncio design.

I don't entirely disagree. I think that the implementation of async
coroutines on top of synchronous coroutines on top of generators is
overly clever and results in a somewhat leaky abstraction and a fair
amount of confusion.



More information about the Python-list mailing list