Thanks for BaseEventLoop.create_task() !

346 views
Skip to first unread message

Paul Sokolovsky

unread,
Nov 5, 2014, 12:17:41 PM11/5/14
to python...@googlegroups.com
Hello,

I'm author of alternative implementation of asyncio subset for
MicroPython, and some time ago I shared by concerns and criticism on
how asyncio API makes it complicated to implement lightweight,
memory-optimized asyncio work-alike:
https://groups.google.com/forum/#!topic/python-tulip/zfMQIUcIR-0

Few days ago, with my better understanding of coroutines and asyncio,
I proceeded to add some thin compatibility later to
MicroPython's uasyncio to use asyncio's methods of coroutine
scheduling (async() and Task()), and to my surprise, founds that
there's now BaseEventLoop.create_task(coro) method
(https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.BaseEventLoop.create_task)
to schedule them in obvious and direct method directly against an
event loop.


Thank you very much for this addition! It being added means that
uasyncio now can be compatible on basic level with asyncio, without
suffering additional memory or runtime overheads, so thanks helping
alternative asyncio implementations which may different architecture
and are resource-constrained (uasyncio for example doesn't have Future
class, and works directly with native Python types of routines (i.e.
functions) and coroutines (i.e. generators), with heaps size of
reference MicroPython implementation of 128Kb).

There're more things in asyncio API which make life complicated for us
(to be exact, 1 thing which is unavoidably limiting, the rest are just
some improvement ideas), I hope to share them some time later, and hope
for positive and productive attention to them too.


--
Best regards,
Paul mailto:pmi...@gmail.com

Guido van Rossum

unread,
Nov 7, 2014, 3:15:36 PM11/7/14
to Paul Sokolovsky, python-tulip
Thanks for your support, Paul. We're looking forward to your further ideas and criticisms.
--
--Guido van Rossum (python.org/~guido)

Victor Stinner

unread,
Nov 11, 2014, 7:10:02 PM11/11/14
to Paul Sokolovsky, python-tulip
Hi,

2014-11-05 18:17 GMT+01:00 Paul Sokolovsky <pmi...@gmail.com>:
> I'm author of alternative implementation of asyncio subset for
> MicroPython,

Oh, interesting. Where can I find your project? Is it fully compatible
with asyncio?

> my surprise, founds that
> there's now BaseEventLoop.create_task(coro) method
> (https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.BaseEventLoop.create_task)
> to schedule them in obvious and direct method directly against an
> event loop.
> Thank you very much for this addition! (...)

FYI I added this method for the greenio project, to support other
implementation of event loops.

I would like to replace all direct calls to Task with create_task, but
I had some issues in the documentation. For Python 3.4 documentation,
Task constructor may be preferred because create_task is only
available since Python 3.4.2 :-/

Victor

Paul Sokolovsky

unread,
Nov 12, 2014, 6:18:37 PM11/12/14
to Victor Stinner, python-tulip
Hello,

On Wed, 12 Nov 2014 01:09:41 +0100
Victor Stinner <victor....@gmail.com> wrote:

> Hi,
>
> 2014-11-05 18:17 GMT+01:00 Paul Sokolovsky <pmi...@gmail.com>:
> > I'm author of alternative implementation of asyncio subset for
> > MicroPython,
>
> Oh, interesting. Where can I find your project? Is it fully compatible
> with asyncio?

It's here:
https://github.com/micropython/micropython-lib/tree/master/uasyncio.core
https://github.com/micropython/micropython-lib/tree/master/uasyncio

Core event loop and subclass implementing async I/O support using
select.poll()-like implementation.

It implements only subset of asyncio functionality, generally the aim
was to make the minimal possible implementation of coroutine
scheduling and async socket I/O which asyncio-like interface.

The biggest aim I'd dream to achieve is that uasyncio code was runnable
on asyncio as is (without compromising efficiency on uasyncio side),
there's no talk about the opposite direction.

>
> > my surprise, founds that
> > there's now BaseEventLoop.create_task(coro) method
> > (https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.BaseEventLoop.create_task)
> > to schedule them in obvious and direct method directly against an
> > event loop.
> > Thank you very much for this addition! (...)
>
> FYI I added this method for the greenio project, to support other
> implementation of event loops.
>
> I would like to replace all direct calls to Task with create_task, but
> I had some issues in the documentation. For Python 3.4 documentation,
> Task constructor may be preferred because create_task is only
> available since Python 3.4.2 :-/

Well, I'm at least glad to see asyncio evolve to accommodate new
usecases, and not being set in stone. In that regard, now that
BaseEventLoop with .create_task() already acquired knowledge about
existence of native Python generators (which previously was layered
away in Task class), do you think that it might be possible to go step
further and allow scheduling a new coroutine by just yielding a
generator instance? In other words:


=======
def task1():
yield from [1, 2, 3]


# in some other coroutine
def task2():
...
yield task1()
...
=======

The idea is certainly not mine, I saw it in David Beazley's
presentations, http://www.dabeaz.com/generators/ , and possibly in some
other pre-asyncio coroutine frameworks. This scheduling form looks
pretty natural and intuitive. Yield can of course return some handle
(Task instance in case of asyncio) to allow to synchronize with started
coroutine.

>
> Victor

Victor Stinner

unread,
Nov 12, 2014, 7:39:15 PM11/12/14
to Paul Sokolovsky, python-tulip
2014-11-13 0:18 GMT+01:00 Paul Sokolovsky <pmi...@gmail.com>:
> (...) do you think that it might be possible to go step
> further and allow scheduling a new coroutine by just yielding a
> generator instance? In other words:

(I don't see how it is related to create_task. create_task is nothing
new, it's just an helper to not have to pass the loop as a parameter.)

"yield from" is more efficient because it requires less iterations in
the event loop.

In Trollius, you *can* (and must) write "yield From(coro)". From(obj)
just returns obj, it's just here to ease transition to asyncio later,
so in fact it's just" yield coro". Because of the syntax of coroutines
(yield vs yield-from), Trollius is less efficient.

Trollius calls _run_once() (one iteration of the event loop) at least
2x more times. The factor depends on the depth of your coroutine.

asyncio example:
---
import asyncio

class EventLoop(asyncio.SelectorEventLoop):
_iteration = 0

def _run_once(self):
self._iteration += 1
super(EventLoop, self)._run_once()

def func4():
# dummy generator
yield from []

def func3():
yield from func4()

def func2():
yield from func3()

def func1():
yield from func2()
yield from func2()

loop = EventLoop()
loop.run_until_complete(func1())
print("iteration# = %s" % loop._iteration)
---

Number of iterations:
- func4() -> 2
- func3() -> 2
- func2() -> 2
- func1() -> 2


Trollius example:
---
import trollius

class EventLoop(trollius.SelectorEventLoop):
_iteration = 0

def _run_once(self):
self._iteration += 1
super(EventLoop, self)._run_once()

def func4():
# dummy generator
yield None

def func3():
yield func4()

def func2():
yield func3()

def func1():
yield func2()
yield func2()

loop = EventLoop()
loop.run_until_complete(func1())
print("iteration# = %s" % loop._iteration)
---

Number of iterations:
- func4() -> 3
- func3() -> 5
- func2() -> 7
- func1() -> 16


Now imagine than calling _run_once() takes 1 ms. With asyncio, the
program will takes 2 ms. With Trollius, it takes between 3 and 16 ms,
depending on the "depth" of the coroutine.

To understand why the number of iterations has a cost, you also have
to know that Trollius creates a new task for each layer of coroutine.
For example func3() creates a task to execute func4(), and func2()
will indirectly creates 2 tasks.

When a coroutine waits for a new task, it has to register itself with
add_done_callback(). When the waited task is done, the _wakeup()
callback is scheduled. Later, _wakeup() calls _step(). etc.

Well, dig into Task._step() of the Trollius project to try to
understand how yield-from solved real issues and makes the code more
efficient and faster ;-)

It was a pain to write the code to support "yield" instead of
"yield-from" in Trollius...

Victor

Paul Sokolovsky

unread,
Nov 12, 2014, 11:33:56 PM11/12/14
to Victor Stinner, python-tulip
Hello,

On Thu, 13 Nov 2014 01:38:53 +0100
Victor Stinner <victor....@gmail.com> wrote:

> 2014-11-13 0:18 GMT+01:00 Paul Sokolovsky <pmi...@gmail.com>:
> > (...) do you think that it might be possible to go step
> > further and allow scheduling a new coroutine by just yielding a
> > generator instance? In other words:
>
> (I don't see how it is related to create_task. create_task is nothing
> new, it's just an helper to not have to pass the loop as a parameter.)

Why, directly related. The talk is about replacing
"loop.create_task(coro)" with "yield coro". "yield from" is not related
in any way. In the case of "yield coro", the loop targeted is implicitly
the current running loop, just the same as "yield from asyncio.sleep()",
etc. Of course, yield can be used only in coroutines, so doesn't
replace .create_task() completely, which still may be needed in
"bootstrap" code.


Here're are examples:

Before:

==========
try:
import uasyncio.core as asyncio
except:
import asyncio

def do_little():
for i in range(3):
print(i)
yield

def main():
for i in range(5):
loop.create_task(do_little())
yield

import logging
logging.basicConfig(level=logging.INFO)
loop = asyncio.get_event_loop()
loop.create_task(main())
loop.run_forever()
==========

after:

==========
try:
import uasyncio.core as asyncio
except:
import asyncio

def do_little():
for i in range(3):
print(i)
yield

def main():
for i in range(5):
yield do_little()

import logging
logging.basicConfig(level=logging.INFO)
loop = asyncio.get_event_loop()
loop.create_task(main())
loop.run_forever()
==========


Excuse the boilerplate code, tested to run on MicroPython too. The diff
is:

- loop.create_task(do_little())
- yield
+ yield do_little()

Guido van Rossum

unread,
Nov 12, 2014, 11:52:28 PM11/12/14
to Paul Sokolovsky, Victor Stinner, python-tulip
No.

The reason is that "yield <coroutine>" is intentionally an error, because it is likely a misspelling for "yield from <coroutine>" -- which means something completely different from what you are proposing.

Paul Sokolovsky

unread,
Nov 13, 2014, 1:09:31 AM11/13/14
to Guido van Rossum, Victor Stinner, python-tulip
Hello,

On Wed, 12 Nov 2014 20:52:07 -0800
Guido van Rossum <gu...@python.org> wrote:

> No.
>
> The reason is that "yield <coroutine>" is intentionally an error,
> because it is likely a misspelling for "yield from <coroutine>" --
> which means something completely different from what you are
> proposing.

Well, there definitely *can* be confusion, as Victor's reply shows
(well, maybe I'm missing something from his reply too ;-) ). But let's
be fair - the ability to use asyncio at all, and efficiently in
particular, depends on understanding of differences between "yield",
"yield from", and normal call operator. And there're bigger confusions
than "yield" vs "yield from".

For example, the biggest issue I've personally hit so far is forgetting
to use "yield from" at all. So, I write "stream.read()", and then
wonder why nothing works. I do that on MicroPython/uasyncio of course,
and in such mediated way understand why there's @asyncio.coroutine
decorator and that envvar which, being set, dumps non-iterated
coroutines at the program termination.

But I wouldn't consciously confuse "yield" and "yield from" - they are
rather different, each serving its own purpose. Maybe that's because
I spent quite some time implementing "yield from" in MicroPython, so
had to understand it well enough ;-).

Speaking of which, to help with my understanding on "yield from" at
that time, I drew a diagram, kind of infographic. I wanted to elaborate
it beyond "a personal note", but never got to it, so I wonder if I
should share it is as after all, maybe some Python developers will find
it funny: https://dl.dropboxusercontent.com/u/44884329/yield-from.pdf
Reply all
Reply to author
Forward
0 new messages