Getting Started with asyncio¶
Building Understanding¶
The terminology and jargon around concurrency, asynchronous communication, and coroutines can obscure the different concepts of what the asyncio module provides in Python.
This section strives to break down the jargon and increase understanding about asyncio by:
- sharing the ultimate goal of asyncio
- explaining asynchronous programming and its comparison to synchronous programming
- describing what a coroutine is
- creating a timeline of the different coroutine approaches used in Python’s history
An Important Goal¶
“The ultimate goal is to help establish a common, easily approachable, mental model of asynchronous programming in Python and make it as close to synchronous programming as possible.” - PEP 492
Understanding asynchronous programming¶
Why asynchronous programming?¶
- web growth
- many long lived connections
- efficiency of resources
Asynchronous vs Synchronous¶
synchronous - step by step through time; meticulous can’t go out of order
asynchronous - planning a party; workers do what they can do until they reach a roadblock or bottleneck; when the roadblock or bottleneck is removed; continue working
From Generators to Native Coroutines¶
Slightly complicating the understanding of coroutines in Python is that there exists more than one implementation of coroutines. As Python grew and evolved, the language added generators, then coroutines via enhanced generators, and recently coroutines using async/await in the asyncio module.
Generators -> Coroutines (generator-based) via Enhanced Generators -> Coroutines (native) in asyncio
What is a Generator?¶
Generators are almost coroutines. Generators can not yield control easily. Pass values into a generator when it has suspended. Generators don’t allow easy cleanup.
Using generators to create limited coroutines¶
Initially, when PEP 342 was accepted, coroutine behavior was created by enhancing Python Generators. We’ll refer to these as generator-based coroutines.
PEP 342 Coroutines via Enhanced Generators - added yield to generators - yield (like pause in a video game) - closely tied to generators
Native Coroutines¶
Beginning with PEP 492 and Python 3.6, a proper standalone concept of coroutines was added.
We refer to these as native coroutines, and these native coroutines rely on the async
and
await
keywords.
PEP 492 Coroutines with async/await syntax - provides a proper standalone concept of coroutines - adds syntax to support this concept
Most often, especially when writing new code, you will choose to use native coroutines (async
and await
).
Overview of the asyncio module¶
High-level asyncio¶
High-level async/await¶
async
andawait
are reserved keywords.
A basic tutorial¶
- show how to use asyncio.run()
- basic functions like asyncio.sleep()
- teach that asyncio programs are all about async/await and not about callbacks or event loops
High-level APIs¶
Tasks, Streams, Subprocesses, few other functions
Low-level APIs¶
Preface¶
talk a bit about everything: - what’s an event loop, - what is a Future - what is a Transport)
Futures¶
Event loop APIs¶
Transports and Protocols¶
(when to use and when not to use them)
Tutorials¶
- High-level networking server
- HTTP application
- Low-level protocol implementation using Transports
- etc
Resources¶
- Yury’s 2017 PyCon talk
- Other PyCon talks
- Caleb Hattingh’s Book
- Guido/Jesse asyncio paper
- Brett’s blog post
Background from release notes¶
PEP 567 Context Variables: The new
contextvars
module and a set of new C APIs introduce support for context variables. Context variables are conceptually similar to thread-local variables. Unlike TLS, context variables support asynchronous code correctly.The
asyncio
anddecimal
modules have been updated to use and support context variables out of the box. Particularly the active decimal context is now stored in a context variable, which allows decimal operations to work with the correct context in asynchronous code.
asyncio¶
The asyncio
module has received many new features, usability and
performance improvements. Notable changes
include:
The new provisional
asyncio.run()
function can be used to run a coroutine from synchronous code by automatically creating and destroying the event loop. (Contributed by Yury Selivanov in :issue:`32314`.)asyncio gained support for
contextvars
.loop.call_soon()
,loop.call_soon_threadsafe()
,loop.call_later()
,loop.call_at()
, andFuture.add_done_callback()
have a new optional keyword-only context parameter.Tasks
now track their context automatically. See PEP 567 for more details. (Contributed by Yury Selivanov in :issue:`32436`.)The new
asyncio.create_task()
function has been added as a shortcut toasyncio.get_event_loop().create_task()
. (Contributed by Andrew Svetlov in :issue:`32311`.)The new
loop.start_tls()
method can be used to upgrade an existing connection to TLS. (Contributed by Yury Selivanov in :issue:`23749`.)The new
loop.sock_recv_into()
method allows reading data from a socket directly into a provided buffer making it possible to reduce data copies. (Contributed by Antoine Pitrou in :issue:`31819`.)The new
asyncio.current_task()
function returns the currently runningTask
instance, and the newasyncio.all_tasks()
function returns a set of all existingTask
instances in a given loop. TheTask.current_task()
andTask.all_tasks()
methods have been deprecated. (Contributed by Andrew Svetlov in :issue:`32250`.)The new provisional
BufferedProtocol
class allows implementing streaming protocols with manual control over the receive buffer. (Contributed by Yury Selivanov in :issue:`32251`.)The new
asyncio.get_running_loop()
function returns the currently running loop, and raises aRuntimeError
if no loop is running. This is in contrast withasyncio.get_event_loop()
, which will create a new event loop if none is running. (Contributed by Yury Selivanov in :issue:`32269`.)The new
StreamWriter.wait_closed()
coroutine method allows waiting until the stream writer is closed. The newStreamWriter.is_closing()
method can be used to determine if the writer is closing. (Contributed by Andrew Svetlov in :issue:`32391`.)The new
loop.sock_sendfile()
coroutine method allows sending files usingos.sendfile
when possible. (Contributed by Andrew Svetlov in :issue:`32410`.)The new
Task.get_loop()
andFuture.get_loop()
methods return the instance of the loop on which a task or a future were created.Server.get_loop()
allows doing the same forasyncio.Server
objects. (Contributed by Yury Selivanov in :issue:`32415` and Srinivas Reddy Thatiparthy in :issue:`32418`.)It is now possible to control how instances of
asyncio.Server
begin serving. Previously, the server would start serving immediately when created. The new start_serving keyword argument toloop.create_server()
andloop.create_unix_server()
, as well asServer.start_serving()
, andServer.serve_forever()
can be used to decouple server instantiation and serving. The newServer.is_serving()
method returnsTrue
if the server is serving.Server
objects are now asynchronous context managers:srv = await loop.create_server(...) async with srv: # some code # At this point, srv is closed and no longer accepts new connections.
(Contributed by Yury Selivanov in :issue:`32662`.)
Callback objects returned by
loop.call_later()
gained the newwhen()
method which returns an absolute scheduled callback timestamp. (Contributed by Andrew Svetlov in :issue:`32741`.)The
loop.create_datagram_endpoint()
method gained support for Unix sockets. (Contributed by Quentin Dawans in :issue:`31245`.)The
loop.create_connection()
,loop.create_server()
,loop.create_unix_server()
, andloop.create_accepted_socket()
now accept the ssl_handshake_timeout keyword argument. (Contributed by Neil Aspinall in :issue:`29970`.)The new
Handle.cancelled()
method returnsTrue
if the callback was cancelled. (Contributed by Marat Sharafutdinov in :issue:`31943`.)The asyncio source has been converted to use the
async
/await
syntax. (Contributed by Andrew Svetlov in :issue:`32193`.)The new
ReadTransport.is_reading()
method can be used to determine the reading state of the transport. Additionally, calls toReadTransport.resume_reading()
andReadTransport.pause_reading()
are now idempotent. (Contributed by Yury Selivanov in :issue:`32356`.)Loop methods which accept socket paths now support passing path-like objects. (Contributed by Yury Selivanov in :issue:`32066`.)
In
asyncio
TCP sockets on Linux are now created withTCP_NODELAY
flag set by default. (Contributed by Yury Selivanov and Victor Stinner in :issue:`27456`.)Exceptions occurring in cancelled tasks are no longer logged. (Contributed by Yury Selivanov in :issue:`30508`.)
Several asyncio
APIs have been
deprecated.
The asyncio
module received a number of notable optimizations for
commonly used functions:
- The
asyncio.get_event_loop()
function has been reimplemented in C to make it up to 15 times faster. (Contributed by Yury Selivanov in :issue:`32296`.) asyncio.Future
callback management has been optimized. (Contributed by Yury Selivanov in :issue:`32348`.)asyncio.gather()
is now up to 15% faster. (Contributed by Yury Selivanov in :issue:`32355`.)asyncio.sleep()
is now up to 2 times faster when the delay argument is zero or negative. (Contributed by Andrew Svetlov in :issue:`32351`.)- The performance overhead of asyncio debug mode has been reduced. (Contributed by Antoine Pitrou in :issue:`31970`.)
deprecated¶
Support for directly await
-ing instances of asyncio.Lock
and
other asyncio synchronization primitives has been deprecated. An
asynchronous context manager must be used in order to acquire and release
the synchronization resource. See Using locks, conditions and semaphores in the async with statement for more
information.
(Contributed by Andrew Svetlov in :issue:`32253`.)
The asyncio.Task.current_task()
and asyncio.Task.all_tasks()
methods have been deprecated.
(Contributed by Andrew Svetlov in :issue:`32250`.)
3.6
* The asyncio
module has received new features, significant
usability and performance improvements, and a fair amount of bug fixes. Starting with Python 3.6 theasyncio
module is no longer provisional and its API is considered stable.
PEP 525: Asynchronous Generators¶
PEP 492 introduced support for native coroutines and async
/ await
syntax to Python 3.5. A notable limitation of the Python 3.5 implementation
is that it was not possible to use await
and yield
in the same
function body. In Python 3.6 this restriction has been lifted, making it
possible to define asynchronous generators:
async def ticker(delay, to):
"""Yield numbers from 0 to *to* every *delay* seconds."""
for i in range(to):
yield i
await asyncio.sleep(delay)
The new syntax allows for faster and more concise code.
See also
- PEP 525 – Asynchronous Generators
- PEP written and implemented by Yury Selivanov.
PEP 530: Asynchronous Comprehensions¶
PEP 530 adds support for using async for
in list, set, dict
comprehensions and generator expressions:
result = [i async for i in aiter() if i % 2]
Additionally, await
expressions are supported in all kinds
of comprehensions:
result = [await fun() for fun in funcs if await condition()]
See also
- PEP 530 – Asynchronous Comprehensions
- PEP written and implemented by Yury Selivanov.
asyncio¶
Starting with Python 3.6 the asyncio
module is no longer provisional and its
API is considered stable.
Notable changes in the asyncio
module since Python 3.5.0
(all backported to 3.5.x due to the provisional status):
- The
get_event_loop()
function has been changed to always return the currently running loop when called from couroutines and callbacks. (Contributed by Yury Selivanov in :issue:`28613`.) - The
ensure_future()
function and all functions that use it, such asloop.run_until_complete()
, now accept all kinds of awaitable objects. (Contributed by Yury Selivanov.) - New
run_coroutine_threadsafe()
function to submit coroutines to event loops from other threads. (Contributed by Vincent Michel.) - New
Transport.is_closing()
method to check if the transport is closing or closed. (Contributed by Yury Selivanov.) - The
loop.create_server()
method can now accept a list of hosts. (Contributed by Yann Sionneau.) - New
loop.create_future()
method to create Future objects. This allows alternative event loop implementations, such as uvloop, to provide a fasterasyncio.Future
implementation. (Contributed by Yury Selivanov in :issue:`27041`.) - New
loop.get_exception_handler()
method to get the current exception handler. (Contributed by Yury Selivanov in :issue:`27040`.) - New
StreamReader.readuntil()
method to read data from the stream until a separator bytes sequence appears. (Contributed by Mark Korenberg.) - The performance of
StreamReader.readexactly()
has been improved. (Contributed by Mark Korenberg in :issue:`28370`.) - The
loop.getaddrinfo()
method is optimized to avoid calling the systemgetaddrinfo
function if the address is already resolved. (Contributed by A. Jesse Jiryu Davis.) - The
loop.stop()
method has been changed to stop the loop immediately after the current iteration. Any new callbacks scheduled as a result of the last iteration will be discarded. (Contributed by Guido van Rossum in :issue:`25593`.) Future.set_exception
will now raiseTypeError
when passed an instance of theStopIteration
exception. (Contributed by Chris Angelico in :issue:`26221`.)- New
loop.connect_accepted_socket()
method to be used by servers that accept connections outside of asyncio, but that use asyncio to handle them. (Contributed by Jim Fulton in :issue:`27392`.) TCP_NODELAY
flag is now set for all TCP transports by default. (Contributed by Yury Selivanov in :issue:`27456`.)- New
loop.shutdown_asyncgens()
to properly close pending asynchronous generators before closing the loop. (Contributed by Yury Selivanov in :issue:`28003`.) Future
andTask
classes now have an optimized C implementation which makes asyncio code up to 30% faster. (Contributed by Yury Selivanov and INADA Naoki in :issue:`26081` and :issue:`28544`.)