Add section on coroutines and the scheduler.
This commit is contained in:
parent
000070388a
commit
3e3fce8960
135
pep-3156.txt
135
pep-3156.txt
|
@ -16,7 +16,7 @@ This is a proposal for asynchronous I/O in Python 3, starting with
|
||||||
Python 3.3. Consider this the concrete proposal that is missing from
|
Python 3.3. Consider this the concrete proposal that is missing from
|
||||||
PEP 3153. The proposal includes a pluggable event loop API, transport
|
PEP 3153. The proposal includes a pluggable event loop API, transport
|
||||||
and protocol abstractions similar to those in Twisted, and a
|
and protocol abstractions similar to those in Twisted, and a
|
||||||
higher-level scheduler based on yield-from (PEP 380). A reference
|
higher-level scheduler based on ``yield from`` (PEP 380). A reference
|
||||||
implementation is in the works under the code name tulip.
|
implementation is in the works under the code name tulip.
|
||||||
|
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ Thus, two separate APIs are defined:
|
||||||
An event loop implementation may provide additional methods and
|
An event loop implementation may provide additional methods and
|
||||||
guarantees.
|
guarantees.
|
||||||
|
|
||||||
The event loop interface does not depend on yield-from. Rather, it
|
The event loop interface does not depend on ``yield from``. Rather, it
|
||||||
uses a combination of callbacks, additional interfaces (transports and
|
uses a combination of callbacks, additional interfaces (transports and
|
||||||
protocols), and Futures. The latter are similar to those defined in
|
protocols), and Futures. The latter are similar to those defined in
|
||||||
PEP 3148, but have a different implementation and are not tied to
|
PEP 3148, but have a different implementation and are not tied to
|
||||||
|
@ -59,7 +59,7 @@ expected to use callbacks.
|
||||||
|
|
||||||
For users (like myself) who don't like using callbacks, a scheduler is
|
For users (like myself) who don't like using callbacks, a scheduler is
|
||||||
provided for writing asynchronous I/O code as coroutines using the PEP
|
provided for writing asynchronous I/O code as coroutines using the PEP
|
||||||
380 yield-from expressions. The scheduler is not pluggable;
|
380 ``yield from`` expressions. The scheduler is not pluggable;
|
||||||
pluggability occurs at the event loop level, and the scheduler should
|
pluggability occurs at the event loop level, and the scheduler should
|
||||||
work with any conforming event loop implementation.
|
work with any conforming event loop implementation.
|
||||||
|
|
||||||
|
@ -406,9 +406,10 @@ The internal methods defined in PEP 3148 are not supported.
|
||||||
A ``tulip.Future`` object is not acceptable to the ``wait()`` and
|
A ``tulip.Future`` object is not acceptable to the ``wait()`` and
|
||||||
``as_completed()`` functions in the ``concurrent.futures`` package.
|
``as_completed()`` functions in the ``concurrent.futures`` package.
|
||||||
|
|
||||||
A ``tulip.Future`` object is acceptable to a yield-from expression
|
A ``tulip.Future`` object is acceptable to a ``yield from`` expression
|
||||||
when used in a coroutine. See the section "Coroutines and the
|
when used in a coroutine. This is implemented through the
|
||||||
Scheduler" below.
|
``__iter__()`` interface on the Future. See the section "Coroutines
|
||||||
|
and the Scheduler" below.
|
||||||
|
|
||||||
Transports
|
Transports
|
||||||
----------
|
----------
|
||||||
|
@ -551,11 +552,6 @@ TBD: How do we detect a half-close (``write_eof()`` in our parlance)
|
||||||
initiated by the other end? Does this call connection_lost()? Is the
|
initiated by the other end? Does this call connection_lost()? Is the
|
||||||
protocol then allowed to write more? (I think it should be!)
|
protocol then allowed to write more? (I think it should be!)
|
||||||
|
|
||||||
Coroutines and the Scheduler
|
|
||||||
----------------------------
|
|
||||||
|
|
||||||
TBD.
|
|
||||||
|
|
||||||
Callback Style
|
Callback Style
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
|
@ -583,6 +579,106 @@ and how to override the choice. Probably belongs in the event loop
|
||||||
policy.)
|
policy.)
|
||||||
|
|
||||||
|
|
||||||
|
Coroutines and the Scheduler
|
||||||
|
============================
|
||||||
|
|
||||||
|
This is a separate toplevel section because its status is different
|
||||||
|
from the event loop interface. Usage of coroutines is optional, and
|
||||||
|
it is perfectly fine to write code using callbacks only. On the other
|
||||||
|
hand, there is only one implementation of the scheduler/coroutine API,
|
||||||
|
and if you're using coroutines, that's the one you're using.
|
||||||
|
|
||||||
|
A coroutine is a generator that follows certain conventions. For
|
||||||
|
documentation purposes, all coroutines should be decorated with
|
||||||
|
``@tulip.coroutine``, but this cannot be strictly enforced.
|
||||||
|
|
||||||
|
Coroutines use the ``yield from`` syntax introduced in PEP 380,
|
||||||
|
instead of the original ``yield`` syntax.
|
||||||
|
|
||||||
|
Unfortunately, the word "coroutine", like the word "generator", is
|
||||||
|
used for two different (though related) concepts:
|
||||||
|
|
||||||
|
- The function that defines a coroutine (a function definition
|
||||||
|
decorated with ``tulip.coroutine``). If disambiguation is needed,
|
||||||
|
we call this a *coroutine function*.
|
||||||
|
|
||||||
|
- The object obtained by calling a coroutine function. This object
|
||||||
|
represents a computation or an I/O operation (usually a combination)
|
||||||
|
that will complete eventually. For disambiguation we call it a
|
||||||
|
*coroutine object*.
|
||||||
|
|
||||||
|
Things a coroutine can do:
|
||||||
|
|
||||||
|
- ``result = yield from future`` -- suspends the coroutine until the
|
||||||
|
future is done, then returns the future's result, or raises its
|
||||||
|
exception, which will be propagated.
|
||||||
|
|
||||||
|
- ``result = yield from coroutine`` -- wait for another coroutine to
|
||||||
|
produce a result (or raise an exception, which will be propagated).
|
||||||
|
The ``coroutine`` expression must be a *call* to another coroutine.
|
||||||
|
|
||||||
|
- ``results = yield from tulip.par(futures_and_coroutines)`` -- Wait
|
||||||
|
for a list of futures and/or coroutines to complete and return a
|
||||||
|
list of their results. If one of the futures or coroutines raises
|
||||||
|
an exception, that exception is propagated, after attempting to
|
||||||
|
cancel all other futures and coroutines in the list.
|
||||||
|
|
||||||
|
- ``return result`` -- produce a result to the coroutine that is
|
||||||
|
waiting for this one using ``yield from``.
|
||||||
|
|
||||||
|
- ``raise exception`` -- raise an exception in the coroutine that is
|
||||||
|
waiting for this one using ``yield from``.
|
||||||
|
|
||||||
|
Calling a coroutine does not start its code running -- it is just a
|
||||||
|
generator, and the coroutine object returned by the call is really a
|
||||||
|
generator object, which doesn't do anything until you iterate over it.
|
||||||
|
In the case of a coroutine object, there are two basic ways to start
|
||||||
|
it running: call ``yield from coroutine`` from another coroutine
|
||||||
|
(assuming the other coroutine is already running!), or convert it to a
|
||||||
|
Task.
|
||||||
|
|
||||||
|
Coroutines can only run when the event loop is running.
|
||||||
|
|
||||||
|
Tasks
|
||||||
|
-----
|
||||||
|
|
||||||
|
A Task is an object that manages an independently running coroutine.
|
||||||
|
The Task interface is the same as the Future interface. The task
|
||||||
|
becomes done when its coroutine returns or raises an exception; if it
|
||||||
|
returns a result, that becomes the task's result, if it raises an
|
||||||
|
exception, that becomes the task's exception.
|
||||||
|
|
||||||
|
Canceling a task that's not done yet prevents its coroutine from
|
||||||
|
completing; in this case an exception is thrown into the coroutine
|
||||||
|
that it may catch to further handle cancelation, but it doesn't have
|
||||||
|
to (this is done using the standard ``close()`` method on generators,
|
||||||
|
described in PEP 342).
|
||||||
|
|
||||||
|
The ``par()`` function described above runs coroutines in parallel by
|
||||||
|
converting them to Tasks. (Arguments that are already Tasks or
|
||||||
|
Futures are not converted.)
|
||||||
|
|
||||||
|
Tasks are also useful for interoperating between coroutines and
|
||||||
|
callback-based frameworks like Twisted. After converting a coroutine
|
||||||
|
into a Task, callbacks can be added to the Task.
|
||||||
|
|
||||||
|
You may ask, why not convert all coroutines to Tasks? The
|
||||||
|
``@tulip.coroutine`` decorator could do this. This would slow things
|
||||||
|
down considerably in the case where one coroutine calls another (and
|
||||||
|
so on), as waiting for a "bare" coroutine has much less overhead than
|
||||||
|
waiting for a Future.
|
||||||
|
|
||||||
|
The Scheduler
|
||||||
|
-------------
|
||||||
|
|
||||||
|
The scheduler has no public interface. You interact with it by using
|
||||||
|
``yield from future`` and ``yield from task``. In fact, there is no
|
||||||
|
single object representing the scheduler -- its behavior is
|
||||||
|
implemented by the ``Task`` and ``Future`` classes using only the
|
||||||
|
public interface of the event loop, so it will work with third-party
|
||||||
|
event loop implementations, too.
|
||||||
|
|
||||||
|
|
||||||
Open Issues
|
Open Issues
|
||||||
===========
|
===========
|
||||||
|
|
||||||
|
@ -617,19 +713,32 @@ Open Issues
|
||||||
yield from sch.block_future(f)
|
yield from sch.block_future(f)
|
||||||
res = f.result()
|
res = f.result()
|
||||||
|
|
||||||
|
- Do we need a larger vocabulary of operations for combining
|
||||||
|
coroutines and/or futures? E.g. in addition to par() we could have
|
||||||
|
a way to run several coroutines sequentially (returning all results
|
||||||
|
or passing the result of one to the next and returning the final
|
||||||
|
result?). We might also introduce explicit locks (though these will
|
||||||
|
be a bit of a pain to use, as we can't use the ``with lock: block``
|
||||||
|
syntax). Anyway, I think all of these are easy enough to write
|
||||||
|
using ``Task``.
|
||||||
|
|
||||||
|
- Priorities?
|
||||||
|
|
||||||
|
|
||||||
Acknowledgments
|
Acknowledgments
|
||||||
===============
|
===============
|
||||||
|
|
||||||
Apart from PEP 3153, influences include PEP 380 and Greg Ewing's
|
Apart from PEP 3153, influences include PEP 380 and Greg Ewing's
|
||||||
tutorial for yield-from, Twisted, Tornado, ZeroMQ, pyftpdlib, tulip
|
tutorial for ``yield from``, Twisted, Tornado, ZeroMQ, pyftpdlib, tulip
|
||||||
(the author's attempts at synthesis of all these), wattle (Steve
|
(the author's attempts at synthesis of all these), wattle (Steve
|
||||||
Dower's counter-proposal), numerous discussions on python-ideas from
|
Dower's counter-proposal), numerous discussions on python-ideas from
|
||||||
September through December 2012, a Skype session with Steve Dower and
|
September through December 2012, a Skype session with Steve Dower and
|
||||||
Dino Viehland, email exchanges with Ben Darnell, an audience with
|
Dino Viehland, email exchanges with Ben Darnell, an audience with
|
||||||
Niels Provos (original author of libevent), and two in-person meetings
|
Niels Provos (original author of libevent), and two in-person meetings
|
||||||
with several Twisted developers, including Glyph, Brian Warner, David
|
with several Twisted developers, including Glyph, Brian Warner, David
|
||||||
Reid, and Duncan McGreggor.
|
Reid, and Duncan McGreggor. Also, the author's previous work on async
|
||||||
|
support in the NDB library for Google App Engine was an important
|
||||||
|
influence.
|
||||||
|
|
||||||
|
|
||||||
Copyright
|
Copyright
|
||||||
|
|
Loading…
Reference in New Issue