Asynchronous Concurrency Concepts¶
Most programs using other concurrency models are written linearly, and
rely on the underlying threading or process management of the language
runtime or operating system to change context as appropriate. An
application based on asyncio
requires the application code to
explicitly handle context changes, and using the techniques for doing
that correctly depends on understanding several inter-related
concepts.
The framework provided by asyncio
is centered on an event
loop, a first class object responsible for efficiently handling I/O
events, system events, and application context changes. Several loop
implementations are provided, to take advantage of operating system
capabilities efficiently. While a reasonable default is usually
selected automatically, it is also possible to pick a particular event
loop implementation from within the application. This is useful under
Windows, for example, where some loop classes add support for external
processes in a way that may trade some efficiencies in network I/O.
An application interacts with the event loop explicitly to register code to be run, and lets the event loop make the necessary calls into application code when resources are available. For example, a network server opens sockets and then registers them to be told when input events occur on them. The event loop alerts the server code when there is a new incoming connection or when there is data to read. Application code is expected to yield control again after a short period of time when no more work can be done in the current context. For example, if there is no more data to read from a socket the server should yield control back to the event loop.
The mechanism for yielding control back to the event loop depends on
Python’s coroutines, special functions that give up control to the
caller without losing their state. Coroutines are similar to generator
functions, and in fact generators can be used to implement coroutines
in versions of Python earlier than 3.5 without native support for
coroutine objects. asyncio
also provides a class-based
abstraction layer for protocols and transports for writing code
using callbacks instead of writing coroutines directly. In both the
class-based and coroutine models, explicitly changing context by
re-entering the event loop takes the place of implicit context changes
in Python’s threading implementation.
A future is a data structure representing the result of work that
has not been completed yet. The event loop can watch for a
Future
object to be set to done, allowing one part of an
application to wait for another part to finish some work. Besides
futures, asyncio
includes other concurrency primitives such as
locks and semaphores.
A Task
is a subclass of Future
that knows how to
wrap and manage the execution for a coroutine. Tasks can be scheduled
with the event loop to run when the resources they need are available,
and to produce a result that can be consumed by other coroutines.