[Python-Dev] Minimal async event loop and async utilities (Was: PEP 492: async/await in Python; version 4)

Paul Moore p.f.moore at gmail.com
Mon May 11 22:37:09 CEST 2015


On 6 May 2015 at 16:46, Guido van Rossum <guido at python.org> wrote:
> This is actually a great idea, and I encourage you to go forward with it.
> The biggest piece missing from your inventory is probably Task, which is
> needed to wrap a Future around a coroutine.

OK, I've been doing some work on this. You're right, the asyncio
framework makes Future a key component.

But I'm not 100% sure why Future (and Task) have to be so fundamental.
Ignoring cancellation (see below!) I can build pretty much all of a
basic event loop, plus equivalents of the asyncio locks and queues
modules, without needing the concept of a Future at all. The
create_task function becomes simply a function to add a coroutine to
the ready queue, in this context. I can't return a Task (because I
haven't implemented the Task or Future classes) but I don't actually
know what significant functionality is lost as a result - is there a
reasonably accessible example of where using the return value from
create_task is important anywhere?

A slightly more complicated issue is with the run_until_complete
function, which takes a Future, and hence is fundamentally tied to the
Future API. However, it seems to me that a "minimal" implementation
could work by having a run_until_complete() that just took an
awaitable (i.e., anything that you can yield from). Again, is there a
specific reason that you ended up going with run_until_complete taking
a Future rather than just a coroutine? I think (but haven't confirmed
yet by implementing it) that it should be possible to create a
coroutine that acts like a Future, in the sense that you can tell it
from outside (via send()) that it's completed and set its return
value. But this is all theory, and if you have any practical
experience that shows I'm going down a dead end, I'd be glad to know.

I'm not sure how useful this line of attack will be - if the API isn't
compatible with asyncio.BaseEventLoop, it's not very useful in
practice. On the other hand, if I can build a loop without Future or
Task classes, it may indicate that those classes aren't quite as
fundamental as asyncio makes them (which may allow some
simplifications or generalisations).

> I expect you'll also want to build cancellation into your "base async
> framework"; and the primitives to wait for multiple awaitables. The next
> step would be some mechanism to implement call_later()/call_at() (but this
> needs to be pluggable since for a "real" event loop it needs to be
> implemented by the basic I/O selector).

These are where I suspect I'll have the most trouble if I haven't got
a solid understanding of the role of the Future and Task classes (or
alternatively, how to avoid them :-)) So I'm holding off on worrying
about them for now. But certainly they need to be covered. In
particular, call_later/call_at are the only "generic" example of any
form of wait that actually *waits*, rather than returning immediately.
So as you say, implementing them will show how the basic mechanism can
be extended with a "real" selector (whether for I/O, or GUI events, or
whatever).

> If you can get this working it would be great to include this in the stdlib
> as a separate "asynclib" library. The original asyncio library would then be
> a specific implementation (using a subclass of asynclib.EventLoop) that adds
> I/O, subprocesses, and integrates with the selectors module (or with IOCP,
> on Windows).

One thing I've not really considered in the above, is how a
refactoring like this would work. Ignoring the "let's try to remove
the Future class" approach above, my "basic event loop" is mostly just
an alternative implementation of an event loop (or maybe an
alternative policy - I'm not sure I understand the need for policies
yet). So it may simply be a case of ripping coroutines.py, futures.py,
locks.py, log.py, queues.py, and tasks.py out of asyncio and adding a
new equivalent of events.py with my "minimal" loop in it. (So far,
when I've tried to do that I get hit with some form of circular import
problem - I've not worked out why yet, or how asyncio avoids the same
problem).

That in itself would probably be a useful refactoring, splitting out
the IO aspects of asyncio from the event loop / async aspects.

> I don't see any particular hurry to get this in before 3.5; the refactoring
> of asyncio can be done later, in a backward compatible way. It would be a
> good way to test the architecture of asyncio!

Agreed. It's also not at all clear to me how the new async/await
syntax would fit in with this, so that probably needs some time to
settle down. For example, in Python 3.5 would run_until_complete take
an awaitable rather than a Future?

Paul


More information about the Python-Dev mailing list