PEP 3156 – Asynchronous IO Support Rebooted: the “asyncio” Module | peps.python.org (original) (raw)

Author:

Guido van Rossum

BDFL-Delegate:

Antoine Pitrou

Discussions-To:

python-tulip@googlegroups.com

Status:

Final

Type:

Standards Track

Created:

12-Dec-2012

Python-Version:

3.3

Post-History:

21-Dec-2012

Replaces:

3153

Resolution:

Python-Dev message


Table of Contents

Abstract

This is a proposal for asynchronous I/O in Python 3, starting at Python 3.3. Consider this the concrete proposal that is missing fromPEP 3153. The proposal includes a pluggable event loop, transport and protocol abstractions similar to those in Twisted, and a higher-level scheduler based on yield from (PEP 380). The proposed package name is asyncio.

Introduction

Status

A reference implementation exists under the code name Tulip. The Tulip repo is linked from the References section at the end. Packages based on this repo will be provided on PyPI (see References) to enable using the asyncio package with Python 3.3 installations.

As of October 20th 2013, the asyncio package has been checked into the Python 3.4 repository and released with Python 3.4-alpha-4, with “provisional” API status. This is an expression of confidence and intended to increase early feedback on the API, and not intended to force acceptance of the PEP. The expectation is that the package will keep provisional status in Python 3.4 and progress to final status in Python 3.5. Development continues to occur primarily in the Tulip repo, with changes occasionally merged into the CPython repo.

Dependencies

Python 3.3 is required for many of the proposed features. The reference implementation (Tulip) requires no new language or standard library features beyond Python 3.3, no third-party modules or packages, and no C code, except for the (optional) IOCP support on Windows.

Module Namespace

The specification here lives in a new top-level package, asyncio. Different components live in separate submodules of the package. The package will import common APIs from their respective submodules and make them available as package attributes (similar to the way the email package works). For such common APIs, the name of the submodule that actually defines them is not part of the specification. Less common APIs may have to explicitly be imported from their respective submodule, and in this case the submodule name is part of the specification.

Classes and functions defined without a submodule name are assumed to live in the namespace of the top-level package. (But do not confuse these with methods of various classes, which for brevity are also used without a namespace prefix in certain contexts.)

Interoperability

The event loop is the place where most interoperability occurs. It should be easy for (Python 3.3 ports of) frameworks like Twisted, Tornado, or even gevents to either adapt the default event loop implementation to their needs using a lightweight adapter or proxy, or to replace the default event loop implementation with an adaptation of their own event loop implementation. (Some frameworks, like Twisted, have multiple event loop implementations. This should not be a problem since these all have the same interface.)

In most cases it should be possible for two different third-party frameworks to interoperate, either by sharing the default event loop implementation (each using its own adapter), or by sharing the event loop implementation of either framework. In the latter case two levels of adaptation would occur (from framework A’s event loop to the standard event loop interface, and from there to framework B’s event loop). Which event loop implementation is used should be under control of the main program (though a default policy for event loop selection is provided).

For this interoperability to be effective, the preferred direction of adaptation in third party frameworks is to keep the default event loop and adapt it to the framework’s API. Ideally all third party frameworks would give up their own event loop implementation in favor of the standard implementation. But not all frameworks may be satisfied with the functionality provided by the standard implementation.

In order to support both directions of adaptation, two separate APIs are specified:

An event loop implementation may provide additional methods and guarantees, as long as these are called out in the documentation as non-standard. An event loop implementation may also leave certain methods unimplemented if they cannot be implemented in the given environment; however, such deviations from the standard API should be considered only as a last resort, and only if the platform or 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 await/yield from. Rather, it uses a combination of callbacks, additional interfaces (transports and protocols), and Futures. The latter are similar to those defined inPEP 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 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, 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 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 conforming implementations.)

For interoperability between code written using coroutines and other async frameworks, the scheduler defines a Task class that behaves like a Future. A framework that interoperates at the event loop level can 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.

If such a framework cannot use the Future and Task classes as-is, it may reimplement the loop.create_future() andloop.create_task() methods. These should return objects implementing (a superset of) the Future/Task interfaces.

A less ambitious framework may just call theloop.set_task_factory() to replace the Task class without implementing its own event loop.

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 there is a method to schedule a callback with an event loop from another thread in a thread-safe manner.

Transports and Protocols

For those not familiar with Twisted, a quick explanation of the relationship between transports and protocols is in order. At the highest level, the transport is concerned with how bytes are transmitted, while the protocol determines which bytes to transmit (and to some extent when).

A different way of saying the same thing: a transport is an abstraction for a socket (or similar I/O endpoint) while a protocol is an abstraction for an application, from the transport’s point of view.

Yet another view is simply that the transport and protocol interfaces_together_ define an abstract interface for using network I/O and interprocess I/O.

There is almost always a 1:1 relationship between transport and protocol objects: the protocol calls transport methods to send data, while the transport calls protocol methods to pass it data that has been received. Neither transport nor protocol methods “block” – they set events into motion and then return.

The most common type of transport is a bidirectional stream transport. It represents a pair of buffered streams (one in each direction) that each transmit a sequence of bytes. The most common example of a bidirectional stream transport is probably a TCP connection. Another common example is an SSL/TLS connection. But there are some other things that can be viewed this way, for example an SSH session or a pair of UNIX pipes. Typically there aren’t many different transport implementations, and most of them come with the event loop implementation. However, there is no requirement that all transports must be created by calling an event loop method: a third party module may well implement a new transport and provide a constructor or factory function for it that simply takes an event loop as an argument or calls get_event_loop().

Note that transports don’t need to use sockets, not even if they use TCP – sockets are a platform-specific implementation detail.

A bidirectional stream transport has two “ends”: one end talks to the network (or another process, or whatever low-level interface it wraps), and the other end talks to the protocol. The former uses whatever API is necessary to implement the transport; but the interface between transport and protocol is standardized by this PEP.

A protocol can represent some kind of “application-level” protocol such as HTTP or SMTP; it can also implement an abstraction shared by multiple protocols, or a whole application. A protocol’s primary interface is with the transport. While some popular protocols (and other abstractions) may have standard implementations, often applications implement custom protocols. It also makes sense to have libraries of useful third party protocol implementations that can be downloaded and installed from PyPI.

There general notion of transport and protocol includes other interfaces, where the transport wraps some other communication abstraction. Examples include interfaces for sending and receiving datagrams (e.g. UDP), or a subprocess manager. The separation of concerns is the same as for bidirectional stream transports and protocols, but the specific interface between transport and protocol is different in each case.

Details of the interfaces defined by the various standard types of transports and protocols are given later.

Event Loop Interface Specification

Event Loop Policy: Getting and Setting the Current Event Loop

Event loop management is controlled by an event loop policy, which is a global (per-process) object. There is a default policy, and an API to change the policy. A policy defines the notion of context; a policy manages a separate event loop per context. The default policy’s notion of context is defined as the current thread.

Certain platforms or programming frameworks may change the default policy to something more suitable to the expectations of the users of that platform or framework. Such platforms or frameworks must document their policy and at what point during their initialization sequence the policy is set, in order to avoid undefined behavior when multiple active frameworks want to override the default policy. (See also “Embedded Event Loops” below.)

To get the event loop for current context, use get_event_loop(). This returns an event loop object implementing the interface specified below, or raises an exception in case no event loop has been set for the current context and the current policy does not specify to create one. It should never return None.

To set the event loop for the current context, useset_event_loop(event_loop), where event_loop is an event loop object, i.e. an instance of AbstractEventLoop, or None. It is okay to set the current event loop to None, in which case subsequent calls to get_event_loop() will raise an exception. This is useful for testing code that should not depend on the existence of a default event loop.

It is expected that get_event_loop() returns a different event loop object depending on the context (in fact, this is the definition of context). It may create a new event loop object if none is set and creation is allowed by the policy. The default policy will create a new event loop only in the main thread (as defined by threading.py, which uses a special subclass for the main thread), and only ifget_event_loop() is called before set_event_loop() is ever called. (To reset this state, reset the policy.) In other threads an event loop must be explicitly set. Other policies may behave differently. Event loop by the default policy creation is lazy; i.e. the first call to get_event_loop() creates an event loop instance if necessary and specified by the current policy.

For the benefit of unit tests and other special cases there’s a third policy function: new_event_loop(), which creates and returns a new event loop object according to the policy’s default rules. To make this the current event loop, you must call set_event_loop() with it.

To change the event loop policy, callset_event_loop_policy(policy), where policy is an event loop policy object or None. If not None, the policy object must be an instance of AbstractEventLoopPolicy that defines methodsget_event_loop(), set_event_loop(loop) andnew_event_loop(), all behaving like the functions described above.

Passing a policy value of None restores the default event loop policy (overriding the alternate default set by the platform or framework). The default event loop policy is an instance of the classDefaultEventLoopPolicy. The current event loop policy object can be retrieved by calling get_event_loop_policy().

TBD: describe child watchers and UNIX quirks for subprocess processing.

Passing an Event Loop Around Explicitly

It is possible to write code that uses an event loop without relying on a global or per-thread default event loop. For this purpose, all APIs that need access to the current event loop (and aren’t methods on an event class) take an optional keyword argument named loop. If this argument is None or unspecified, such APIs will callget_event_loop() to get the default event loop, but if theloop keyword argument is set to an event loop object, they will use that event loop, and pass it along to any other such APIs they call. For example, Future(loop=my_loop) will create a Future tied to the event loop my_loop. When the default current event isNone, the loop keyword argument is effectively mandatory.

Note that an explicitly passed event loop must still belong to the current thread; the loop keyword argument does not magically change the constraints on how an event loop can be used.

Specifying Times

As usual in Python, all timeouts, intervals and delays are measured in seconds, and may be ints or floats. However, absolute times are not specified as POSIX timestamps. The accuracy, precision and epoch of the clock are up to the implementation.

The default implementation uses time.monotonic(). Books could be written about the implications of this choice. Better read the docs for the standard library time module.

Embedded Event Loops

On some platforms an event loop is provided by the system. Such a loop may already be running when the user code starts, and there may be no way to stop or close it without exiting from the program. In this case, the methods for starting, stopping and closing the event loop may not be implementable, and is_running() may always returnTrue.

Event Loop Classes

There is no actual class named EventLoop. There is anAbstractEventLoop class which defines all the methods without implementations, and serves primarily as documentation. The following concrete classes are defined:

Event Loop Methods Overview

The methods of a conforming event loop are grouped into several categories. The first set of categories must be supported by all conforming event loop implementations, with the exception that embedded event loops may not implement the methods for starting, stopping and closing. (However, a partially-conforming event loop is still better than nothing. :-)

The second set of categories may be supported by conforming event loop implementations. If not supported, they will raiseNotImplementedError. (In the default implementation,SelectorEventLoop on UNIX systems supports all of these;SelectorEventLoop on Windows supports the I/O event handling category; ProactorEventLoop on Windows supports the pipes and subprocess category.)

Event Loop Methods

Starting, Stopping and Closing

An (unclosed) event loop can be in one of two states: running or stopped. These methods deal with starting and stopping an event loop:

Basic Callbacks

Callbacks associated with the same event loop are strictly serialized: one callback must finish before the next one will be called. This is an important guarantee: when two or more callbacks use or modify shared state, each callback is guaranteed that while it is running, the shared state isn’t changed by another callback.

Note: A previous version of this PEP defined a method namedcall_repeatedly(), which promised to call a callback at regular intervals. This has been withdrawn because the design of such a function is overspecified. On the one hand, a simple timer loop can easily be emulated using a callback that reschedules itself usingcall_later(); it is also easy to write coroutine containing a loop and a sleep() call (a toplevel function in the module, see below). On the other hand, due to the complexities of accurate timekeeping there are many traps and pitfalls here for the unaware (see PEP 418), and different use cases require different behavior in edge cases. It is impossible to offer an API for this purpose that is bullet-proof in all cases, so it is deemed better to let application designers decide for themselves what kind of timer loop to implement.

Thread interaction

See also the wrap_future() function described in the section about Futures.

Internet name lookups

These methods are useful if you want to connect or bind a socket to an address without the risk of blocking for the name lookup. They are usually called implicitly by create_connection(),create_server() or create_datagram_endpoint().

Internet connections

These are the high-level interfaces for managing internet connections. Their use is recommended over the corresponding lower-level interfaces because they abstract away the differences between selector-based and proactor-based event loops.

Note that the client and server side of stream connections use the same transport and protocol interface. However, datagram endpoints use a different transport and protocol interface.

Wrapped Socket Methods

The following methods for doing async I/O on sockets are not for general use. They are primarily meant for transport implementations working with IOCP through the ProactorEventLoop class. However, they are easily implementable for other event loop types, so there is no reason not to require them. The socket argument has to be a non-blocking socket.

I/O Callbacks

These methods are primarily meant for transport implementations working with a selector. They are implemented bySelectorEventLoop but not by ProactorEventLoop. Custom event loop implementations may or may not implement them.

The fd arguments below may be integer file descriptors, or “file-like” objects with a fileno() method that wrap integer file descriptors. Not all file-like objects or file descriptors are acceptable. Sockets (and socket file descriptors) are always accepted. On Windows no other types are supported. On UNIX, pipes and possibly tty devices are also supported, but disk files are not. Exactly which special file types are supported may vary by platform and per selector implementation. (Experimentally, there is at least one kind of pseudo-tty on OS X that is supported by select andpoll but not by kqueue: it is used by Emacs shell windows.)

Pipes and Subprocesses

These methods are supported by SelectorEventLoop on UNIX andProactorEventLoop on Windows.

The transports and protocols used with pipes and subprocesses differ from those used with regular stream connections. These are described later.

Each of the methods below has a protocol_factory argument, similar to create_connection(); this will be called exactly once, without arguments, to construct the protocol object to be returned.

Each method is a coroutine that returns a (transport, protocol)pair on success, or raises an exception on failure.

Apart from the way the program to execute is specified, the twosubprocess_*() methods behave the same. The transport returned is a SubprocessTransport which has a different interface than the common bidirectional stream transport. The protocol returned is aSubprocessProtocol which also has a custom interface.

The are all specified using optional keyword arguments:

Signal callbacks

These methods are only supported on UNIX.

Note: If these methods are statically known to be unsupported, they may raise NotImplementedError instead of RuntimeError.

Mutual Exclusion of Callbacks

An event loop should enforce mutual exclusion of callbacks, i.e. it should never start a callback while a previously callback is still running. This should apply across all types of callbacks, regardless of whether they are scheduled using call_soon(), call_later(),call_at(), call_soon_threadsafe(), add_reader(),add_writer(), or add_signal_handler().

Exceptions

There are two categories of exceptions in Python: those that derive from the Exception class and those that derive fromBaseException. Exceptions deriving from Exception will generally be caught and handled appropriately; for example, they will be passed through by Futures, and they will be logged and ignored when they occur in a callback.

However, exceptions deriving only from BaseException are typically not caught, and will usually cause the program to terminate with a 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 the latter category into its exception handler. This is a callback which accepts a context dict as a parameter:

def exception_handler(context): ...

context may have many different keys but several of them are very widely used:

The loop has the following methods related to exception handling:

Debug Mode

By default the loop operates in release mode. Applications may enable debug mode better error reporting at the cost of some performance.

In debug mode many additional checks are enabled, for example:

There are two methods related to debug mode:

Debug mode is automatically enabled if the PYTHONASYNCIODEBUG environment variable is defined and not empty.

Handles

The various methods for registering one-off callbacks (call_soon(), call_later(), call_at() andcall_soon_threadsafe()) all return an object representing the registration that can be used to cancel the callback. This object is called a Handle. Handles are opaque and have only one public method:

Note that add_reader(), add_writer() andadd_signal_handler() do not return Handles.

Servers

The create_server() method returns a Server instance, which wraps the sockets (or other network objects) used to accept requests. This class has two public methods:

Futures

The asyncio.Future class here is intentionally similar to theconcurrent.futures.Future class specified by PEP 3148, but there are slight differences. Whenever this PEP talks about Futures or futures this should be understood to refer to asyncio.Future unlessconcurrent.futures.Future is explicitly mentioned. The supported public API is as follows, indicating the differences with PEP 3148:

The internal method set_running_or_notify_cancel() is not supported; there is no way to set the running state. Likewise, the method running() is not supported.

The following exceptions are defined:

A Future is associated with an event loop when it is created.

A asyncio.Future object is not acceptable to the wait() andas_completed() functions in the concurrent.futures package. However, there are similar APIs asyncio.wait() andasyncio.as_completed(), described below.

A asyncio.Future object is acceptable to a yield from expression when used in a coroutine. This is implemented through the__iter__() interface on the Future. See the section “Coroutines and the Scheduler” below.

When a Future is garbage-collected, if it has an associated exception but neither result() nor exception() has ever been called, the exception is logged. (When a coroutine uses yield from to wait for a Future, that Future’s result() method is called once the coroutine is resumed.)

In the future (pun intended) we may unify asyncio.Future andconcurrent.futures.Future, e.g. by adding an __iter__() method to the latter that works with yield from. To prevent accidentally blocking the event loop by calling e.g. result() on a Future that’s not done yet, the blocking operation may detect that an event loop is active in the current thread and raise an exception instead. However the current PEP strives to have no dependencies beyond Python 3.3, so changes to concurrent.futures.Future are off the table for now.

There are some public functions related to Futures:

Transports

Transports and protocols are strongly influenced by Twisted and PEP 3153. Users rarely implement or instantiate transports – rather, event loops offer utility methods to set up transports.

Transports work in conjunction with protocols. Protocols are typically written without knowing or caring about the exact type of transport used, and transports can be used with a wide variety of protocols. For example, an HTTP client protocol implementation may be used with either a plain socket transport or an SSL/TLS transport. The plain socket transport can be used with many different protocols besides HTTP (e.g. SMTP, IMAP, POP, FTP, IRC, SPDY).

The most common type of transport is a bidirectional stream transport. There are also unidirectional stream transports (used for pipes) and datagram transports (used by the create_datagram_endpoint()method).

Methods For All Transports

Bidirectional Stream Transports

A bidirectional stream transport is an abstraction on top of a socket or something similar (for example, a pair of UNIX pipes or an SSL/TLS connection).

Most connections have an asymmetric nature: the client and server usually have very different roles and behaviors. Hence, the interface between transport and protocol is also asymmetric. From the protocol’s point of view, writing data is done by calling thewrite() method on the transport object; this buffers the data and returns immediately. However, the transport takes a more active role in reading data: whenever some data is read from the socket (or other data source), the transport calls the protocol’sdata_received() method.

Nevertheless, the interface between transport and protocol used by bidirectional streams is the same for clients as it is for servers, since the connection between a client and a server is essentially a pair of streams, one in each direction.

Bidirectional stream transports have the following public methods:

Unidirectional Stream Transports

A writing stream transport supports the write(), writelines(),write_eof(), can_write_eof(), close() and abort() methods described for bidirectional stream transports.

A reading stream transport supports the pause_reading(),resume_reading() and close() methods described for bidirectional stream transports.

A writing stream transport calls only connection_made() andconnection_lost() on its associated protocol.

A reading stream transport can call all protocol methods specified in the Protocols section below (i.e., the previous two plusdata_received() and eof_received()).

Datagram Transports

Datagram transports have these methods:

Datagram transports call the following methods on the associated protocol object: connection_made(), connection_lost(),error_received() and datagram_received(). (“Connection” in these method names is a slight misnomer, but the concepts still exist: connection_made() means the transport representing the endpoint has been created, and connection_lost() means the transport is closed.)

Subprocess Transports

Subprocess transports have the following methods:

Note that send_signal(), terminate() and kill() wrap the corresponding methods in the standard library subprocess module.

Protocols

Protocols are always used in conjunction with transports. While a few common protocols are provided (e.g. decent though not necessarily excellent HTTP client and server implementations), most protocols will be implemented by user code or third-party libraries.

Like for transports, we distinguish between stream protocols, datagram protocols, and perhaps other custom protocols. The most common type of protocol is a bidirectional stream protocol. (There are no unidirectional protocols.)

Stream Protocols

A (bidirectional) stream protocol must implement the following methods, which will be called by the transport. Think of these as callbacks that are always called by the event loop in the right context. (See the “Context” section way above.)

Here is a table indicating the order and multiplicity of the basic calls:

  1. connection_made() – exactly once
  2. data_received() – zero or more times
  3. eof_received() – at most once
  4. connection_lost() – exactly once

Calls to pause_writing() and resume_writing() occur in pairs and only between #1 and #4. These pairs will not be nested. The final resume_writing() call may be omitted; i.e. a paused connection may be lost and never be resumed.

Datagram Protocols

Datagram protocols have connection_made() andconnection_lost() methods with the same signatures as stream protocols. (As explained in the section about datagram transports, we prefer the slightly odd nomenclature over defining different method names to indicating the opening and closing of the socket.)

In addition, they have the following methods:

Here is a chart indicating the order and multiplicity of calls:

  1. connection_made() – exactly once
  2. datagram_received(), error_received() – zero or more times
  3. connection_lost() – exactly once

Subprocess Protocol

Subprocess protocols have connection_made(), connection_lost(),pause_writing() and resume_writing() methods with the same signatures as stream protocols. In addition, they have the following methods:

Note that depending on the behavior of the subprocess it is possible that process_exited() is called either before or afterpipe_connection_lost(). For example, if the subprocess creates a sub-subprocess that shares its stdin/stdout/stderr and then itself exits, process_exited() may be called while all the pipes are still open. On the other hand, when the subprocess closes its stdin/stdout/stderr but does not exit, pipe_connection_lost() may be called for all three pipes without process_exited() being called. If (as is the more common case) the subprocess exits and thereby implicitly closes all pipes, the calling order is undefined.

Callback Style

Most interfaces taking a callback also take positional arguments. For instance, to arrange for foo("abc", 42) to be called soon, you call loop.call_soon(foo, "abc", 42). To schedule the callfoo(), use loop.call_soon(foo). This convention greatly reduces the number of small lambdas required in typical callback programming.

This convention specifically does not support keyword arguments. Keyword arguments are used to pass optional extra information about the callback. This allows graceful evolution of the API without having to worry about whether a keyword might be significant to a callee somewhere. If you have a callback that must be called with a keyword argument, you can use a lambda. For example:

loop.call_soon(lambda: foo('abc', repeat=42))

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.

Coroutines

A coroutine is a generator that follows certain conventions. For documentation purposes, all coroutines should be decorated with@asyncio.coroutine, but this cannot be strictly enforced.

Coroutines use the yield from syntax introduced in PEP 380, instead of the original yield syntax.

The word “coroutine”, like the word “generator”, is used for two different (though related) concepts:

Things a coroutine can do:

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 (see below).

Coroutines (and tasks) can only run when the event loop is running.

Waiting for Multiple Coroutines

To wait for multiple coroutines or Futures, two APIs similar to thewait() and as_completed() APIs in the concurrent.futurespackage are provided:

Sleeping

The coroutine asyncio.sleep(delay) returns after a given time delay.

Tasks

A Task is an object that manages an independently running coroutine. The Task interface is the same as the Future interface, and in factTask is a subclass of Future. 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.

Cancelling a task that’s not done yet throws anasyncio.CancelledError exception into the coroutine. If the coroutine doesn’t catch this (or if it re-raises it) the task will be marked as cancelled (i.e., cancelled() will return True); but if the coroutine somehow catches and ignores the exception it may continue to execute (and cancelled() will return False).

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.

To convert a coroutine into a task, call the coroutine function and 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 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.

The Task class is derived from Future adding new methods:

The Scheduler

The scheduler has no public interface. You interact with it by usingyield 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.

Convenience Utilities

A few functions and classes are provided to simplify the writing of basic stream-based clients and servers, such as FTP or HTTP. These are:

Synchronization

Locks, events, conditions and semaphores modeled after those in thethreading module are implemented and can be accessed by importing the asyncio.locks submodule. Queues modeled after those in thequeue module are implemented and can be accessed by importing theasyncio.queues submodule.

In general these have a close correspondence to their threaded counterparts, however, blocking methods (e.g. acquire() on locks,put() and get() on queues) are coroutines, and timeout parameters are not provided (you can use asyncio.wait_for() to add a timeout to a blocking call, however).

The docstrings in the modules provide more complete documentation.

Locks

The following classes are provided by asyncio.locks. For all these except Event, the with statement may be used in combination with yield from to acquire the lock and ensure that the lock is released regardless of how the with block is left, as follows:

with (yield from my_lock): ...

Queues

The following classes and exceptions are provided by asyncio.queues.

Miscellaneous

Logging

All logging performed by the asyncio package uses a singlelogging.Logger object, asyncio.logger. To customize logging you can use the standard Logger API on this object. (Do not replace the object though.)

SIGCHLD handling on UNIX

Efficient implementation of the process_exited() method on subprocess protocols requires a SIGCHLD signal handler. However, signal handlers can only be set on the event loop associated with the main thread. In order to support spawning subprocesses from event loops running in other threads, a mechanism exists to allow sharing aSIGCHLD handler between multiple event loops. There are two additional functions, asyncio.get_child_watcher() andasyncio.set_child_watcher(), and corresponding methods on the event loop policy.

There are two child watcher implementation classes,FastChildWatcher and SafeChildWatcher. Both use SIGCHLD. The SafeChildWatcher class is used by default; it is inefficient when many subprocesses exist simultaneously. The FastChildWatcherclass is efficient, but it may interfere with other code (either C code or Python code) that spawns subprocesses without using anasyncio event loop. If you are sure you are not using other code that spawns subprocesses, to use the fast implementation, run the following in your main thread:

watcher = asyncio.FastChildWatcher() asyncio.set_child_watcher(watcher)

Wish List

(There is agreement that these features are desirable, but no implementation was available when Python 3.4 beta 1 was released, and the feature freeze for the rest of the Python 3.4 release cycle prohibits adding them in this late stage. However, they will hopefully be added in Python 3.5, and perhaps earlier in the PyPI distribution.)

Former wish list items that have since been implemented (but aren’t specified by the PEP):

Open Issues

(Note that these have been resolved de facto in favor of the status quo by the acceptance of the PEP. However, the PEP’s provisional status allows revising these decisions for Python 3.5.)

References

Acknowledgments

Apart from PEP 3153, influences include PEP 380 and Greg Ewing’s tutorial for yield from, Twisted, Tornado, ZeroMQ, pyftpdlib, and wattle (Steve Dower’s counter-proposal). My previous work on asynchronous support in the NDB library for Google App Engine provided an important starting point.

I am grateful for the numerous discussions on python-ideas from September through December 2012, and many more on python-tulip since then; a Skype session with Steve Dower and Dino Viehland; email exchanges with and a visit by Ben Darnell; an audience with Niels Provos (original author of libevent); and in-person meetings (as well as frequent email exchanges) with several Twisted developers, including Glyph, Brian Warner, David Reid, and Duncan McGreggor.

Contributors to the implementation include Eli Bendersky, Gustavo Carneiro (Gambit Research), Saúl Ibarra Corretgé, Geert Jansen, A. Jesse Jiryu Davis, Nikolay Kim, Charles-François Natali, Richard Oudkerk, Antoine Pitrou, Giampaolo Rodolá, Andrew Svetlov, and many others who submitted bugs and/or fixes.

I thank Antoine Pitrou for his feedback in his role of official PEP BDFL.

This document has been placed in the public domain.