Update for asyncio PEP (#105)

This commit is contained in:
Andrew Svetlov 2016-11-04 00:21:23 +02:00 committed by Guido van Rossum
parent 4f899a0f01
commit 317cede6ec
1 changed files with 116 additions and 9 deletions

View File

@ -116,25 +116,26 @@ environment forces the issue. (An example would be a platform where
there is a system event loop that cannot be started or stopped; see
"Embedded Event Loops" below.)
The event loop API does not depend on ``yield from``. Rather, it uses
The event loop API does not depend on ``await/yield from``. Rather, it uses
a combination of callbacks, additional interfaces (transports and
protocols), and Futures. The latter are similar to those defined in
PEP 3148, but have a different implementation and are not tied to
threads. In particular, the ``result()`` method raises an exception
instead of blocking when a result is not yet ready; the user is
expected to use callbacks (or ``yield from``) to wait for the result.
expected to use callbacks (or ``await/yield from``) to wait for the result.
All event loop methods specified as returning a coroutine are allowed
to return either a Future or a coroutine, at the implementation's
choice (the standard implementation always returns coroutines). All
event loop methods documented as accepting coroutine arguments *must*
accept both Futures and coroutines for such arguments. (A convenience
function, ``async()``, exists to convert an argument that is either a
function, ``ensure_future()``, exists to convert an argument that is either a
coroutine or a Future into a Future.)
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
380 ``yield from`` expressions. The scheduler is not pluggable;
380 ``yield from`` or PEP 492 ``await`` expressions.
The scheduler is not pluggable;
pluggability occurs at the event loop level, and the standard
scheduler implementation should work with any conforming event loop
implementation. (In fact this is an important litmus test for
@ -147,6 +148,15 @@ wait for a Future to complete by adding a callback to the Future.
Likewise, the scheduler offers an operation to suspend a coroutine
until a callback is called.
In case of any other async framework cannot just use Future and Task
as is for some reason the framework may reimplement
``loop.create_future()`` and ``loop.create_task()`` calls by returning
an object which implements a superset of Future/Task interfaces but
adds a new functionality required by the framework.
``loop.set_task_factory()`` may be useful as well for adopting asyncio
tasks without implementing own event loop by a framework.
The event loop API provides limited interoperability with threads:
there is an API to submit a function to an executor (see PEP 3148)
which returns a Future that is compatible with the event loop, and
@ -286,6 +296,8 @@ framework). The default event loop policy is an instance of the class
``DefaultEventLoopPolicy``. The current event loop policy object can
be retrieved by calling ``get_event_loop_policy()``.
TBD: decribe child watchers and UNIX quirks for subprocess processing
Passing an Event Loop Around Explicitly
'''''''''''''''''''''''''''''''''''''''
@ -361,7 +373,8 @@ stopping and closing. (However, a partially-conforming event loop is
still better than nothing. :-)
- Starting, stopping and closing: ``run_forever()``,
``run_until_complete()``, ``stop()``, ``is_running()``, ``close()``.
``run_until_complete()``, ``stop()``, ``is_running()``, ``close()``,
``is_closed()``.
- Basic and timed callbacks: ``call_soon()``, ``call_later()``,
``call_at()``, ``time()``.
@ -377,6 +390,14 @@ still better than nothing. :-)
- Wrapped socket methods: ``sock_recv()``, ``sock_sendall()``,
``sock_connect()``, ``sock_accept()``.
- Tasks and futures support: ``create_future()``, ``create_task``,
``set_task_factory()``, ``get_task_factory()``.
- Error handling: ``get_exception_handler()``, ``set_exception_handler()``,
``default_exception_handler()``, ``call_exception_handler()``.
- Debug mode: ``get_debug()``, ``set_debug()``.
The second set of categories *may* be supported by conforming event
loop implementations. If not supported, they will raise
``NotImplementedError``. (In the default implementation,
@ -416,6 +437,8 @@ stopped. These methods deal with starting and stopping an event loop:
Future is done. If the Future is done, its result is returned, or
its exception is raised. This cannot be called when the event loop
is already running.
The method creates a new ``Task`` object if the
parameter is a coroutine.
- ``stop()``. Stops the event loop as soon as it is convenient. It
is fine to restart the loop with ``run_forever()`` or
@ -438,6 +461,10 @@ stopped. These methods deal with starting and stopping an event loop:
loop should not be used again. It may be called multiple times;
subsequent calls are no-ops.
- ``is_closed()``. Returns ``True`` if the event loop is closed,
``False`` otherwise.
Basic Callbacks
'''''''''''''''
@ -943,6 +970,69 @@ traceback. In some cases they are caught and re-raised. (Examples of
this category include ``KeyboardInterrupt`` and ``SystemExit``; it is
usually unwise to treat these the same as most other exceptions.)
The event loop passes caught but not handled exceptions into its
*exception handler*. The handler is a callback which accepts
*context* dict as a parameter::
def exception_handler(context):
...
*context* may have many different keys but several of them are very
widely used and present in almost all contexts:
- ``'message'``: error message.
- ``'exception'``: exception instance, no exception on a stack if the
value is ``None``.
- ``'source_traceback'``: a list of strigs for stack trace on the
failed object creation moment.
- ``'handle_traceback'``: a list of strigs for stack trace on the
failed handle creation moment.
The loop has the following methods related to exception handling:
- ``get_exception_handler()`` returns the current handler registered
in the loop.
- ``set_exception_handler(handler)`` assings new handler.
- ``default_exception_handler(context)`` is *default* handler for the
loop implementation.
- ``call_exception_handler(context)`` passes *context* into the
registered exception handler. The call allows to handle uncaught
exceptions uniformly by third-party libraries.
The loop uses ``default_exception_handler()`` if the default was not
overridden by explicit ``set_exception_handler()`` call.
Debug Mode
----------
By default the loop operates in *release* mode for sake of performance.
But application may enable *debug* mode.
In this mode many additional checks are enabled, for example:
- source tracebacks are available for unrolled exceptions in futures/tasks.
- loop checks callback execution time for blaming blocking code.
``loop.slow_callback_duration`` attribute controls maximum execution
time for callback or task iteration between two *yield points*. By
default the attribute's value is ``0.1`` (100 milliseconds) but it
may be tuned by user.
There are two methods related to the subject:
- ``get_debug()`` returns ``True`` if *debug* mode is enabled,
``False`` otherwise.
- ``set_debug(enabled)`` enables *debug* mode if the parameter is ``True``.
Debug mode is switched on if ``PYTHONASYNCIODEBUG`` *environment
variable* is defined and not empty.
Handles
-------
@ -1433,8 +1523,7 @@ methods:
- ``pipe_data_received(fd, data)``. Called when the subprocess writes
data to its stdout or stderr. ``fd`` is the file descriptor (1 for
stdout, 2 for stderr). ``data`` is a ``bytes`` object. (TBD: No
``pipe_eof_received()``?)
stdout, 2 for stderr). ``data`` is a ``bytes`` object.
- ``pipe_connection_lost(fd, exc)``. Called when the subprocess
closes its stdin, stdout or stderr. ``fd`` is the file descriptor.
@ -1647,8 +1736,8 @@ callback-based frameworks like Twisted. After converting a coroutine
into a Task, callbacks can be added to the Task.
To convert a coroutine into a task, call the coroutine function and
pass the resulting coroutine object to the ``asyncio.Task()``
constructor. You may also use ``asyncio.async()`` for this purpose.
pass the resulting coroutine object to the ``loop.create_task()``
method. You may also use ``asyncio.ensure_future()`` for this purpose.
You may ask, why not automatically convert all coroutines to Tasks?
The ``@asyncio.coroutine`` decorator could do this. However, this would
@ -1656,6 +1745,22 @@ slow things down considerably in the case where one coroutine calls
another (and so on), as switching to a "bare" coroutine has much less
overhead than switching to a Task.
A Task is inherited from ``Future`` but adds new methods:
- ``current_task(loop=None)``. A *class method* returning the
currently running task in an event loop. If *loop* is ``None`` the
method returns a current task for default loop. Every coroutine is
executed inside a *task context* either by running it by
``ensure_future()`` / ``loop.create_task()`` call or by calling a
nested coroutine by ``await nested()``. The method may return
``None`` if it's called *outside* of coroutine, e.g. in callback
scheduled by ``loop.call_later()``.
- ``all_tasks(loop=None)``. A *class method* returning a set of all
active tasks for the loop. The method uses default loop if *loop* is
``None``.
The Scheduler
-------------
@ -1930,6 +2035,8 @@ status allows revising these decisions for Python 3.5.)
References
==========
- PEP 492 describes the semantics of ``async/await``.
- PEP 380 describes the semantics of ``yield from``.
- Greg Ewing's ``yield from`` tutorials: