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 and await 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 and decimal 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(), and Future.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 to asyncio.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 running Task instance, and the new asyncio.all_tasks() function returns a set of all existing Task instances in a given loop. The Task.current_task() and Task.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 a RuntimeError if no loop is running. This is in contrast with asyncio.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 new StreamWriter.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 using os.sendfile when possible. (Contributed by Andrew Svetlov in :issue:`32410`.)

  • The new Task.get_loop() and Future.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 for asyncio.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 to loop.create_server() and loop.create_unix_server(), as well as Server.start_serving(), and Server.serve_forever() can be used to decouple server instantiation and serving. The new Server.is_serving() method returns True 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 new when() 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(), and loop.create_accepted_socket() now accept the ssl_handshake_timeout keyword argument. (Contributed by Neil Aspinall in :issue:`29970`.)

  • The new Handle.cancelled() method returns True 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 to ReadTransport.resume_reading() and ReadTransport.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 with TCP_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:

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 the asyncio 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 as loop.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 faster asyncio.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 system getaddrinfo 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 raise TypeError when passed an instance of the StopIteration 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 and Task 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`.)