Python Asyncio Guide
Python Asyncio Guide
(https://superfastpython.com/)
This makes asyncio very attractive and widely used for Python web development,
Python APIs that make web calls, and concurrency for socket programming.
Some tips:
1. You may want to bookmark this guide and read it over a few sittings.
2. You can download a zip (https://superfastpython.com/wp-
content/uploads/2023/11/superfastpython-asyncio-guide.zip) of all code used in
this guide.
3. You can get help, ask a question in the comments or email me
(https://superfastpython.com/contact/).
4. You can jump to the topics that interest you via the table of contents (below).
https://superfastpython.com/python-asyncio/ 1/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
Table of Contents
1. What is Asynchronous Programming
1.1. Asynchronous Tasks
1.2. Asynchronous Programming
1.3. Asynchronous Programming in Python
2. What is Asyncio
2.1. Changes to Python to add Support for Coroutines
https://superfastpython.com/python-asyncio/ 2/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
https://superfastpython.com/python-asyncio/ 3/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
https://superfastpython.com/python-asyncio/ 4/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
https://superfastpython.com/python-asyncio/ 5/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
Instead, requests and function calls are issued and executed somehow in the
background at some future time. This frees the caller to perform other activities and
handle the results of issued calls at a later time when results are available or when
the caller is interested.
Asynchronous Tasks
Asynchronous means not at the same time, as opposed to synchronous or at the
same time.
https://superfastpython.com/python-asyncio/ 6/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
This will issue the request to make the function call and will not wait around for the
call to complete. We can choose to check on the status or result of the function call
later.
Asynchronous Function Call: Request that a function is called at some time and
in some manner, allowing the caller to resume and perform other activities.
The function call will happen somehow and at some time, in the background, and the
program can perform other tasks or respond to other events.
This is key. We don’t have control over how or when the request is handled, only that
we would like it handled while the program does other things.
Issuing an asynchronous function call often results in some handle on the request
that the caller can use to check on the status of the call or get results. This is often
called a future.
Future: A handle on an asynchronous function call allowing the status of the call
to be checked and results to be retrieved.
https://superfastpython.com/python-asyncio/ 7/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
The combination of the asynchronous function call and future together is often
referred to as an asynchronous task. This is because it is more elaborate than a
function call, such as allowing the request to be canceled and more.
Asynchronous Programming
Issuing asynchronous tasks and making asynchronous function calls is referred to as
asynchronous programming.
https://superfastpython.com/python-asyncio/ 8/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
“ In non-blocking mode, when we write bytes to a socket, we can just fire and
forget the write or read, and our application can go on to perform other
tasks.
Non-blocking I/O is a way of performing I/O where reads and writes are requested,
although performed asynchronously. The caller does not need to wait for the
operation to complete before returning.
The read and write operations are performed somehow (e.g. by the underlying
operating system or systems built upon it), and the status of the action and/or data is
retrieved by the caller later, once available, or when the caller is ready.
https://superfastpython.com/python-asyncio/ 9/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
It is implemented using coroutines that run in an event loop that itself runs in a single
thread.
More broadly, Python offers threads and processes that can execute tasks
asynchronously.
For example, one thread can start a second thread to execute a function call and
resume other activities. The operating system will schedule and execute the second
thread at some time and the first thread may or may not check on the status of the
https://superfastpython.com/python-asyncio/ 10/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
task, manually.
“ Threads are asynchronous, meaning that they may run at different speeds,
and any thread can halt for an unpredictable duration at any time.
More concretely, Python provides executor-based thread pools and process pools in
the ThreadPoolExecutor (https://superfastpython.com/threadpoolexecutor-in-
python/) and ProcessPoolExeuctor
(https://superfastpython.com/processpoolexecutor-in-python/) classes.
These classes use the same interface and support asynchronous tasks via the
submit() method that returns a Future object.
https://superfastpython.com/python-asyncio/ 11/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
The capabilities of these classes are described in terms of worker execution tasks
asynchronously. They explicitly provide synchronous (blocking) and asynchronous
(non-blocking) versions of each method for executing tasks.
For example, one may issue a one-off function call synchronously via the apply()
method or asynchronously via the apply_async() method.
There are other aspects of asynchronous programming in Python that are less strictly
related to Python concurrency.
For example, Python processes receive or handle signals asynchronously. Signals are
fundamentally asynchronous events sent from other processes.
Now that we know about asynchronous programming, let’s take a closer look at
asyncio.
https://superfastpython.com/python-asyncio/ 12/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
What is Asyncio
Broadly, asyncio refers to the ability to implement asynchronous programming in
Python using coroutines.
1. The addition of the “asyncio” module to the Python standard library in Python
3.4.
2. The addition of async/await expressions to the Python language in Python 3.5.
Together, the module and changes to the language facilitate the development of
Python programs that support coroutine-based concurrency, non-blocking I/O, and
asynchronous programming.
“ Python 3.4 introduced the asyncio library, and Python 3.5 produced the
async and await keywords to use it palatably. These new additions allow so-
called asynchronous programming.
Let’s take a closer look at these two aspects of asyncio, starting with the changes to
the language.
https://superfastpython.com/python-asyncio/ 13/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
For example:
1 # define a coroutine
2 async def custom_coro():
3 # ...
Calling a coroutine function will create a coroutine object, this is a new class. It does
not execute the coroutine function.
1 ...
2 # create a coroutine object
3 coro = custom_coro()
This suspends the caller and schedules the target for execution.
1 ...
2 # suspend and schedule the target
3 await custom_coro()
https://superfastpython.com/python-asyncio/ 14/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
1 ...
2 # traverse an asynchronous iterator
3 async for item in async_iterator:
4 print(item)
Instead, the calling coroutine that executes the for loop will suspend and internally
await each awaitable yielded from the iterator.
An asynchronous context manager is a context manager that can await the enter and
exit methods.
The calling coroutine will suspend and await the context manager before entering the
block for the context manager, and similarly when leaving the context manager block.
https://superfastpython.com/python-asyncio/ 15/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
These are the sum of the major changes to Python language to support coroutines.
“ The event loop is the core of every asyncio application. Event loops run
asynchronous tasks and callbacks, perform network IO operations, and run
subprocesses.
https://superfastpython.com/python-asyncio/ 16/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
The high-level API is for us Python application developers. The low-level API is for
framework developers, not us, in most cases.
Most use cases are satisfied using the high-level API that provides utilities for working
with coroutines, streams, synchronization primitives, subprocesses, and queues for
sharing data between coroutines.
The lower-level API provides the foundation for the high-level API and includes the
internals of the event loop, transport protocols, policies, and more.
Now that we know what asyncio is, broadly, and that it is for Asynchronous
programming.
Next, let’s explore when we should consider using asyncio in our Python programs.
https://superfastpython.com/python-asyncio/ 17/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
They are:
We may want to use coroutines because we can have many more concurrent
coroutines in our program than concurrent threads.
Coroutines are an alternative that is provided by the Python language and runtime
(standard interpreter) and further supported by the asyncio module. They are suited
to non-blocking I/O with subprocesses and sockets, however, blocking I/O and CPU-
bound tasks can be used in a simulated non-blocking manner using threads and
processes under the covers.
https://superfastpython.com/python-asyncio/ 18/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
This last point is subtle and key. Although we can choose to use coroutines for the
capability for which they were introduced into Python, non-blocking, we may in fact
use them with any tasks. Any program written with threads or processes can be
rewritten or instead written using coroutines if we so desire.
Threads and processes achieve multitasking via the operating system that chooses
which threads and processes should run, when, and for how long. The operating
switches between threads and processes rapidly, suspending those that are not
running and resuming those granted time to run. This is called preemptive
multitasking.
This allows coroutines to cooperate by design, choosing how and when to suspend
their execution.
They are more lightweight than threads. This means they are faster to start and use
less memory. Essentially a coroutine is a special type of function, whereas a thread is
represented by a Python object and is associated with a thread in the operating
system with which the object must interact.
https://superfastpython.com/python-asyncio/ 19/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
As such, we may have thousands of threads in a Python program, but we could easily
have tens or hundreds of thousands of coroutines all in one thread.
That is, we want to develop a Python program that uses the asynchronous
programming paradigm.
When programming, asynchronous means that the action is requested, although not
performed at the time of the request. It is performed later.
Asynchronous programming often means going all in and designing the program
around the concept of asynchronous function calls and tasks.
https://superfastpython.com/python-asyncio/ 20/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
We may choose to use asyncio because we want or require non-blocking I/O in our
program.
Hard disk drives: Reading, writing, appending, renaming, deleting, etc. files.
Peripherals: mouse, keyboard, screen, printer, serial, camera, etc.
Internet: Downloading and uploading files, getting a webpage, querying RSS, etc.
Database: Select, update, delete, etc. SQL queries.
Email: Send mail, receive mail, query inbox, etc.
These operations are slow, compared to calculating things with the CPU.
The common way these operations are implemented in programs is to make the read
or write request and then wait for the data to be sent or received.
https://superfastpython.com/python-asyncio/ 21/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
The operating system can see that the calling thread is blocked and will context
switch to another thread that will make use of the CPU.
This means that the blocking call does not slow down the entire system. But it does
halt or block the thread or program making the blocking call.
It requires support in the underlying operating system, just like blocking I/O, and all
modern operating systems provide support for some form of non-blocking I/O.
Non-blocking I/O allows read and write calls to be made as asynchronous requests.
The operating system will handle the request and notify the calling program when the
results are available.
The asyncio module in Python was added specifically to add support for non-blocking
I/O with subprocesses (e.g. executing commands on the operating system) and with
streams (e.g. TCP socket programming) to the Python standard library.
We may choose to use asyncio because we want to use asynchronous I/O in our
program, and that is a defensible reason.
Sometimes we have control over the function and non-functional requirements and
other times not. In the cases we do, we may choose to use asyncio for one of the
reasons listed above. In the cases we don’t, we may be led to choose asyncio in order
to deliver a program that solves a specific problem.
1. Use asyncio because someone else made the decision for you.
2. Use asyncio because the project you have joined is already using it.
3. Use asyncio because you want to learn more about it.
We don’t always have full control over the projects we work on.
It is common to start a new job, new role, or new project and be told by the line
manager or lead architect of specific design and technology decisions.
https://superfastpython.com/python-asyncio/ 23/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
We may use asyncio on a project because the project is already using it. You must use
asyncio, rather than you choose to use asyncio.
A related example might be the case of a solution to a problem that uses asyncio that
you wish to adopt.
For example:
Perhaps you need to use a third-party API and the code examples use asyncio.
Perhaps you need to integrate an existing open-source solution that uses
asyncio.
Perhaps you stumble across some code snippets that do what you need, yet they
use asyncio.
For lack of alternate solutions, asyncio may be thrust upon you by your choice of
solution.
Finally, we may choose asyncio for our Python project to learn more about.
You may choose to adopt asyncio just because you want to try it out and it can be a
defensible reason.
Using asyncio in a project will make its workings concrete for you.
It is probably a good idea to spend at least a moment on why we should not use it.
https://superfastpython.com/python-asyncio/ 24/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
One reason to not use asyncio is that you cannot defend its use using one of the
reasons above.
This is not foolproof. There may be other reasons to use it, not listed above.
But, if you pick a reason to use asyncio and the reason feels thin or full of holes for
your specific case. Perhaps asyncio is not the right solution.
I think the major reason to not use asyncio is that it does not deliver the benefit that
you think it does.
For example:
Only a single coroutine can run at a time by design, they cooperate to execute. This is
just like threads under the GIL. In fact, the GIL is an orthogonal concern and probably
irrelevant in most cases when using asyncio.
Any program you can write with asyncio, you can write with threads and it will
probably be as fast or faster. It will also probably be simpler and easier to read and
interpret by fellow developers.
Any concurrency failure mode you might expect with threads, you can encounter with
coroutines. You must make coroutines safe from deadlocks and race conditions, just
like threads.
https://superfastpython.com/python-asyncio/ 25/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
Another reason to not use asyncio is that you don’t like asynchronous programming.
Asynchronous programming has been popular for some time now in a number of
different programming communities, most notably the JavaScript community.
No problem. If you don’t like it, don’t use it. It’s a fair reason.
You can achieve the same effect in many ways, notably by sprinkling a few
asynchronous calls in via thread or process executors as needed.
Now that we are familiar with when to use asyncio, let’s look at coroutines in more
detail.
Discover how to use the Python asyncio module including how to define, create, and
run new coroutines and how to use non-blocking I/O.
Coroutines in Python
Python provides first-class coroutines with a “coroutine” type and new expressions
like “async def” and “await“.
https://superfastpython.com/python-asyncio/ 26/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
What is a Coroutine
A coroutine is a function (https://en.wikipedia.org/wiki/Coroutine) that can be
suspended and resumed.
A subroutine can be executed, starting at one point and finishing at another point.
Whereas, a coroutine can be executed then suspended, and resumed many times
before finally terminating.
Specifically, coroutines have control over when exactly they suspend their execution.
This may involve the use of a specific expression, such as an “await” expression in
Python, like a yield expression in a Python generator.
A coroutine may suspend for many reasons, such as executing another coroutine, e.g.
awaiting another task, or waiting for some external resources, such as a socket
connection or process to return data.
https://superfastpython.com/python-asyncio/ 27/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
Many coroutines can be created and executed at the same time. They have control
over when they will suspend and resume, allowing them to cooperate as to when
concurrent tasks are executed.
Now that we have some idea of what a coroutine is, let’s deepen this understanding
by comparing them to other familiar programming constructs.
https://superfastpython.com/python-asyncio/ 28/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
The main difference is that it chooses to suspend and resume its execution many
times before returning and exiting.
Both coroutines and subroutines can call other examples of themselves. A subroutine
can call other subroutines. A coroutine executes other coroutines. However, a
coroutine can also execute other subroutines.
https://superfastpython.com/python-asyncio/ 29/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
When a coroutine executes another coroutine, it must suspend its execution and
allow the other coroutine to resume once the other coroutine has completed.
This is like a subroutine calling another subroutine. The difference is the suspension
of the coroutine may allow any number of other coroutines to run as well.
This makes a coroutine calling another coroutine more powerful than a subroutine
calling another subroutine. It is central to the cooperating multitasking facilitated by
coroutines.
Coroutine vs Generator
A generator is a special function that can suspend its execution.
A generator function can be defined like a normal function although it uses a yield
expression at the point it will suspend its execution and return a value.
A generator function will return a generator iterator object that can be traversed,
such as via a for-loop. Each time the generator is executed, it runs from the last point
it was suspended to the next yield statement.
https://superfastpython.com/python-asyncio/ 30/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
Before coroutines were developed, generators were extended so that they might be
used like coroutines in Python programs.
https://superfastpython.com/python-asyncio/ 31/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
This was made possible via changes to the generators and the introduction of the
“yield from” expression.
Coroutine vs Task
A subroutine and a coroutine may represent a “task” in a program.
“ A Future-like object that runs a Python coroutine. […] Tasks are used to run
coroutines in event loops.
This allows the wrapped coroutine to execute in the background. The calling
coroutine can continue executing instructions rather than awaiting another
coroutine.
Coroutine vs Thread
A coroutine is more lightweight than a thread.
A thread is an object created and managed by the underlying operating system and
represented in Python as a threading.Thread object.
This means that coroutines are typically faster to create and start executing and take
up less memory. Conversely, threads are slower than coroutines to create and start
and take up more memory.
https://superfastpython.com/python-asyncio/ 33/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
Coroutines execute within one thread, therefore a single thread may execute many
coroutines.
Coroutine vs Process
A coroutine is more lightweight than a process.
Processes, like threads, are created and managed by the underlying operating system
and are represented by a multiprocessing.Process object.
This means that coroutines are significantly faster than a process to create and start
and take up much less memory.
https://superfastpython.com/python-asyncio/ 34/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
Generators have slowly been migrating towards becoming first-class coroutines for a
long time.
We can explore some of the major changes to Python to add coroutines, which we
might consider a subset of the probability addition of asyncio.
New methods like send() and close() were added to generator objects to allow them
to act more like coroutines.
“ This PEP proposes some enhancements to the API and syntax of generators,
to make them usable as simple coroutines.
https://superfastpython.com/python-asyncio/ 35/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
The vast majority of the capabilities for working with modern coroutines in Python via
the asyncio module were described in PEP 3156 (https://peps.python.org/pep-3156/),
added in Python 3.3.
Coroutines were executed using an asyncio event loop, via the asyncio module.
https://superfastpython.com/python-asyncio/ 36/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
A coroutine could suspend and execute another coroutine via the “yield from”
expression
For example:
https://superfastpython.com/python-asyncio/ 37/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
We might say that coroutines were added as first-class objects to Python in version
3.5.
This included changes to the Python language, such as the “async def“, “await“,
“async with“, and “async for” expressions, as well as a coroutine type.
Now that we know what a coroutine is, let’s take a closer look at how to use them in
Python.
https://superfastpython.com/python-asyncio/ 38/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
The “asyncio” module provides tools to run our coroutine objects in an event loop,
which is a runtime for coroutines.
For example:
1 # define a coroutine
2 async def custom_coro():
3 # ...
A coroutine can then use coroutine-specific expressions within it, such as await,
async for, and async with.
https://superfastpython.com/python-asyncio/ 39/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
For example:
1 # define a coroutine
2 async def custom_coro():
3 # await another coroutine
4 await asyncio.sleep(1)
For example:
1 ...
2 # create a coroutine
3 coro = custom_coro()
https://superfastpython.com/python-asyncio/ 40/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
A “coroutine” Python object has methods, such as send() and close(). It is a type.
We can demonstrate this by creating an instance of a coroutine and calling the type()
built-in function in order to report its type.
For example:
1 # SuperFastPython.com
2 # check the type of a coroutine
3
4 # define a coroutine
5 async def custom_coro():
6 # await another coroutine
7 await asyncio.sleep(1)
8
9 # create the coroutine
10 coro = custom_coro()
11 # check the type of the coroutine
12 print(type(coro))
Running the example reports that the created coroutine is a “coroutine” class.
We also get a RuntimeError because the coroutine was created but never executed,
we will explore that in the next section.
1 <class 'coroutine'>
2 sys:1: RuntimeWarning: coroutine 'custom_coro' was never awaited
https://superfastpython.com/python-asyncio/ 41/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
— AWAITABLE OBJECTS
(HTTPS://DOCS.PYTHON.ORG/3/REFERENCE/DATAMODEL.HTML#COROUTINES)
“ The event loop is the core of every asyncio application. Event loops run
asynchronous tasks and callbacks, perform network IO operations, and run
subprocesses.
The event loop that executes coroutines, manages the cooperative multitasking
between coroutines.
“ Coroutine objects can only run when the event loop is running.
— PAGE 517, PYTHON IN A NUTSHELL (HTTPS://AMZN.TO/3TAZSBW), 2017.
The typical way to start a coroutine event loop is via the asyncio.run() function
(https://docs.python.org/3/library/asyncio-task.html#asyncio.run).
https://superfastpython.com/python-asyncio/ 42/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
This function takes one coroutine and returns the value of the coroutine. The
provided coroutine can be used as the entry point into the coroutine-based program.
For example:
1 # SuperFastPython.com
2 # example of running a coroutine
3 import asyncio
4 # define a coroutine
5 async def custom_coro():
6 # await another coroutine
7 await asyncio.sleep(1)
8
9 # main coroutine
10 async def main():
11 # execute my custom coroutine
12 await custom_coro()
13
14 # start the coroutine program
15 asyncio.run(main())
Now that we know how to define, create, and run a coroutine, let’s take a moment to
understand the event loop.
(https://superfastpython.com/paj-incontent)
Loving The Tutorials?
Why not take the next step? Get the book.
In this section, we will take a moment to look at the asyncio event loop.
https://superfastpython.com/python-asyncio/ 43/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
1. Execute coroutines.
2. Execute callbacks.
3. Perform network input/output.
4. Run subprocesses.
“ The event loop is the core of every asyncio application. Event loops run
asynchronous tasks and callbacks, perform network IO operations, and run
subprocesses.
Event loops are a common design pattern and became very popular in recent times
given their use in JavaScript.
https://superfastpython.com/python-asyncio/ 44/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
The event loop, as its name suggests, is a loop. It manages a list of tasks (coroutines)
and attempts to progress each in sequence in each iteration of the loop, as well as
perform other tasks like executing callbacks and handling I/O.
The “asyncio” module provides functions for accessing and interacting with the event
loop.
Instead, access to the event loop is provided for framework developers, those that
want to build on top of the asyncio module or enable asyncio for their library.
The asyncio module provides a low-level API for getting access to the current event
loop object, as well as a suite of methods that can be used to interact with the event
loop.
https://superfastpython.com/python-asyncio/ 45/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
The low-level API is intended for framework developers that will extend, complement
and integrate asyncio into third-party libraries.
We rarely need to interact with the event loop in asyncio programs, in favor of using
the high-level API instead.
“ This function always creates a new event loop and closes it at the end. It
should be used as a main entry point for asyncio programs, and should
ideally only be called once.
We typically pass it to our main coroutine and run our program from there.
There are low-level functions for creating and accessing the event loop.
https://superfastpython.com/python-asyncio/ 46/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
For example:
1 ...
2 # create and access a new asyncio event loop
3 loop = asyncio.new_event_loop()
In the example below we will create a new event loop and then report its details.
1 # SuperFastPython.com
2 # example of creating an event loop
3 import asyncio
4
5 # create and access a new asyncio event loop
6 loop = asyncio.new_event_loop()
7 # report defaults of the loop
8 print(loop)
Running the example creates the event loop, then reports the details of the object.
We can see that in this case the event loop has the type _UnixSelectorEventLoop
and is not running, but is also not closed.
If an asyncio event loop is already running, we can get access to it via the
asyncio.get_running_loop() function (https://docs.python.org/3/library/asyncio-
eventloop.html#asyncio.get_running_loop).
https://superfastpython.com/python-asyncio/ 47/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
“ Return the running event loop in the current OS thread. If there is no running
event loop a RuntimeError is raised. This function can only be called from a
coroutine or a callback.
For example:
1 ...
2 # access he running event loop
3 loop = asyncio.get_running_loop()
There is also a function for getting or starting the event loop called
asyncio.get_event_loop() (https://docs.python.org/3/library/asyncio-
eventloop.html#asyncio.get_event_loop), but it was deprecated in Python 3.10 and
should not be used.
The event loop object defines how the event loop is implemented and provides a
common API for interacting with the loop, defined on the AbstractEventLoop class
(https://github.com/python/cpython/blob/3.10/Lib/asyncio/events.py#L204).
For example, Windows and Unix-based operations systems will implement the event
loop in different ways, given the different underlying ways that non-blocking I/O is
implemented on these platforms.
https://superfastpython.com/python-asyncio/ 48/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
Third-party libraries may implement their own event loops to optimize for specific
features.
There are many reasons why we may want access to the event loop from outside of a
running asyncio program.
For example:
An asyncio event loop can be used in a program as an alternative to a thread pool for
coroutine-based tasks.
An event loop may also be embedded within a normal asyncio program and accessed
as needed.
Now that we know a little about the event loop, let’s look at asyncio tasks.
https://superfastpython.com/python-asyncio/ 49/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
The asyncio event loop manages tasks. As such, all coroutines become and are
managed as tasks within the event loop.
It provides a handle on a scheduled coroutine that an asyncio program can query and
use to interact with the coroutine.
A task is created from a coroutine. It requires a coroutine object, wraps the coroutine,
schedules it for execution, and provides ways to interact with it.
A task is executed independently. This means it is scheduled in the asyncio event loop
and will execute regardless of what else happens in the coroutine that created it. This
is different from executing a coroutine directly, where the caller must wait for it to
complete.
https://superfastpython.com/python-asyncio/ 50/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
A Future is a lower-level class that represents a result that will eventually arrive.
Classes that extend the Future class are often referred to as Future-like.
Because a Task is awaitable it means that a coroutine can wait for a task to be done
using the await expression.
For example:
https://superfastpython.com/python-asyncio/ 51/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
1 ...
2 # wait for a task to be done
3 await task
Now that we know what an asyncio task is, let’s look at how we might create one.
Recall that a coroutine is defined using the async def expression and looks like a
function.
For example:
1 # define a coroutine
2 async def task_coroutine():
3 # ...
There are two main ways to create and schedule a task, they are:
For example:
https://superfastpython.com/python-asyncio/ 52/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
1 ...
2 # create a coroutine
3 coro = task_coroutine()
4 # create a task from a coroutine
5 task = asyncio.create_task(coro)
For example:
1 ...
2 # create a task from a coroutine
3 task = asyncio.create_task(task_coroutine())
The task instance can be discarded, interacted with via methods, and awaited by a
coroutine.
This is the preferred way to create a Task from a coroutine in an asyncio program.
A task can also be created from a coroutine using the lower-level asyncio API.
This function takes a Task, Future, or Future-like object, such as a coroutine, and
optionally the loop in which to schedule it.
https://superfastpython.com/python-asyncio/ 53/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
For example:
1 ...
2 # create and schedule the task
3 task = asyncio.ensure_future(task_coroutine())
Another low-level function that we can use to create and schedule a Task is the
loop.create_task() method (https://docs.python.org/3/library/asyncio-
eventloop.html#asyncio.loop.create_task).
This function requires access to a specific event loop in which to execute the
coroutine as a task.
We can acquire an instance to the current event loop within an asyncio program via
the asyncio.get_event_loop() function (https://docs.python.org/3/library/asyncio-
eventloop.html#asyncio.get_event_loop).
This can then be used to call the create_task() method to create a Task instance and
schedule it for execution.
For example:
1 ...
2 # get the current event loop
3 loop = asyncio.get_event_loop()
4 # create and schedule the task
5 task = loop.create_task(task_coroutine())
In fact, the task will not execute until the event loop has an opportunity to run.
https://superfastpython.com/python-asyncio/ 54/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
This will not happen until all other coroutines are not running and it is the task’s turn
to run.
For example, if we had an asyncio program with one coroutine that created and
scheduled a task, the scheduled task will not run until the calling coroutine that
created the task is suspended.
This may happen if the calling coroutine chooses to sleep, chooses to await another
coroutine or task, or chooses to await the new task that was scheduled.
For example:
1 ...
2 # create a task from a coroutine
3 task = asyncio.create_task(task_coroutine())
4 # await the task, allowing it to run
5 await task
You can learn more about how to create asyncio tasks in the tutorial:
Now that we know what a task is and how to schedule them, next, let’s look at how
we may use them in our programs.
In this section, we will take a closer look at how to interact with them in our
programs.
Task Life-Cycle
An asyncio Task has a life cycle.
https://superfastpython.com/python-asyncio/ 55/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
1. Created
2. Scheduled
2a Canceled
3. Running
3a. Suspended
3b. Result
3c. Exception
3d. Canceled
4. Done
Note that Suspended, Result, Exception, and Canceled are not states per se, they are
important points of transition for a running task.
The diagram below summarizes this life cycle showing the transitions between each
phase.
https://superfastpython.com/python-asyncio/ 56/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
You can learn more about the asyncio task life-cycle in the tutorial:
Now that we are familiar with the life cycle of a task from a high level, let’s take a
closer look at each phase.
https://superfastpython.com/python-asyncio/ 57/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
For example:
1 ...
2 # check if a task is done
3 if task.done():
4 # ...
A task is done if it has had the opportunity to run and is now no longer running.
https://superfastpython.com/python-asyncio/ 58/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
The method returns True if the task was canceled, or False otherwise.
For example:
1 ...
2 # check if a task was canceled
3 if task.cancelled():
4 # ...
A task is canceled if the cancel() method was called on the task and completed
successfully, e..g cancel() returned True.
A task is not canceled if the cancel() method was not called, or if the cancel() method
was called but failed to cancel the task.
This returns the return value of the coroutine wrapped by the Task or None if the
wrapped coroutine does not explicitly return a value.
For example:
1 ...
2 # get the return value from the wrapped coroutine
3 value = task.result()
For example:
https://superfastpython.com/python-asyncio/ 59/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
1 ...
2 try:
3 # get the return value from the wrapped coroutine
4 value = task.result()
5 except Exception:
6 # task failed and there is no result
If the task was canceled, then a CancelledError exception is raised when calling the
result() method and may need to be handled.
For example:
1 ...
2 try:
3 # get the return value from the wrapped coroutine
4 value = task.result()
5 except asyncio.CancelledError:
6 # task was canceled
For example:
1 ...
2 # check if the task was not canceled
3 if not task.cancelled():
4 # get the return value from the wrapped coroutine
5 value = task.result()
6 else:
7 # task was canceled
If the task is not yet done, then an InvalidStateError exception is raised when calling
the result() method and may need to be handled.
For example:
1 ...
2 try:
3 # get the return value from the wrapped coroutine
4 value = task.result()
5 except asyncio.InvalidStateError:
6 # task is not yet done
For example:
https://superfastpython.com/python-asyncio/ 60/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
1 ...
2 # check if the task is not done
3 if not task.done():
4 await task
5 # get the return value from the wrapped coroutine
6 value = task.result()
We can retrieve an unhandled exception in the coroutine wrapped by a task via the
exception() method (https://docs.python.org/3/library/asyncio-
task.html#asyncio.Task.exception).
For example:
1 ...
2 # get the exception raised by a task
3 exception = task.exception()
If an unhandled exception was not raised in the wrapped coroutine, then a value of
None is returned.
If the task was canceled, then a CancelledError exception is raised when calling the
exception() method and may need to be handled.
For example:
1 ...
2 try:
3 # get the exception raised by a task
4 exception = task.exception()
5 except asyncio.CancelledError:
6 # task was canceled
For example:
https://superfastpython.com/python-asyncio/ 61/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
1 ...
2 # check if the task was not canceled
3 if not task.cancelled():
4 # get the exception raised by a task
5 exception = task.exception()
6 else:
7 # task was canceled
If the task is not yet done, then an InvalidStateError exception is raised when calling
the exception() method and may need to be handled.
For example:
1 ...
2 try:
3 # get the exception raised by a task
4 exception = task.exception()
5 except asyncio.InvalidStateError:
6 # task is not yet done
For example:
1 ...
2 # check if the task is not done
3 if not task.done():
4 await task
5 # get the exception raised by a task
6 exception = task.exception()
The cancel method returns True if the task was canceled, or False otherwise.
For example:
1 ...
2 # cancel the task
3 was_cancelled = task.cancel()
If the task is already done, it cannot be canceled and the cancel() method will return
False and the task will not have the status of canceled.
https://superfastpython.com/python-asyncio/ 62/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
The next time the task is given an opportunity to run, it will raise a CancelledError
exception.
If the CancelledError exception is not handled within the wrapped coroutine, the
task will be canceled.
The cancel() method can also take a message argument which will be used in the
content of the CancelledError.
This method takes the name of a function to call when the task is done.
For example:
Recall that a task may be done when the wrapped coroutine finishes normally when it
returns, when an unhandled exception is raised or when the task is canceled.
https://superfastpython.com/python-asyncio/ 63/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
For example:
1 ...
2 # remove a done callback function
3 task.remove_done_callback(handle)
This name can be helpful if multiple tasks are created from the same coroutine and
we need some way to tell them apart programmatically.
The name can be set when the task is created from a coroutine via the “name”
argument.
For example:
1 ...
2 # create a task from a coroutine
3 task = asyncio.create_task(task_coroutine(), name='MyTask')
The name for the task can also be set via the set_name() method
(https://docs.python.org/3/library/asyncio-task.html#asyncio.Task.set_name).
For example:
1 ...
2 # set the name of the task
3 task.set_name('MyTask')
For example:
https://superfastpython.com/python-asyncio/ 64/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
1 ...
2 # get the name of a task
3 name = task.get_name()
You can learn more about checking the status of tasks in the tutorial:
This can be achieved by getting an asyncio.Task object for the currently running task
and for all tasks that are running.
This function will return a Task object for the task that is currently running.
For example:
1 ...
2 # get the current task
3 task = asyncio.current_task()
This will return a Task object for the currently running task.
https://superfastpython.com/python-asyncio/ 65/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
A task may create and run another coroutine (e.g. not wrapped in a task). Getting the
current task from within a coroutine will return a Task object for the running task, but
not the coroutine that is currently running.
Getting the current task can be helpful if a coroutine or task requires details about
itself, such as the task name for logging.
We can explore how to get a Task instance for the main coroutine used to start an
asyncio program.
The example below defines a coroutine used as the entry point into the program. It
reports a message, then gets the current task and reports its details.
This is an important first example, as it highlights that all coroutines can be accessed
as tasks within the asyncio event loop.
1 # SuperFastPython.com
2 # example of getting the current task from the main coroutine
3 import asyncio
4
5 # define a main coroutine
6 async def main():
7 # report a message
8 print('main coroutine started')
9 # get the current task
10 task = asyncio.current_task()
11 # report its details
12 print(task)
13
14 # start the asyncio program
15 asyncio.run(main())
Running the example first creates the main coroutine and uses it to start the asyncio
program.
It then retrieves the current task, which is a Task object that represents itself, the
currently running coroutine.
https://superfastpython.com/python-asyncio/ 66/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
We can see that the task has the default name for the first task, ‘Task-1‘ and is
executing the main() coroutine, the currently running coroutine.
This highlights that we can use the asyncio.current_task() function to access a Task
object for the currently running coroutine, that is automatically wrapped in a Task
object.
You can learn more about getting the current task in the tutorial:
We can get a set of all scheduled and running (not yet done) tasks in an asyncio
program via the asyncio.all_tasks() function.
For example:
1 ...
2 # get all tasks
3 tasks = asyncio.all_tasks()
https://superfastpython.com/python-asyncio/ 67/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
The set will also include a task for the currently running task, e.g. the task that is
executing the coroutine that calls the asyncio.all_tasks() function.
Also, recall that the asyncio.run() method that is used to start an asyncio program
will wrap the provided coroutine in a task. This means that the set of all tasks will
include the task for the entry point of the program.
We can explore the case where we have many tasks within an asyncio program and
then get a set of all tasks.
In this example, we first create 10 tasks, each wrapping and running the same
coroutine.
The main coroutine then gets a set of all tasks scheduled or running in the program
and reports their details.
https://superfastpython.com/python-asyncio/ 68/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
1 # SuperFastPython.com
2 # example of starting many tasks and getting access to all tasks
3 import asyncio
4
5 # coroutine for a task
6 async def task_coroutine(value):
7 # report a message
8 print(f'task {value} is running')
9 # block for a moment
10 await asyncio.sleep(1)
11
12 # define a main coroutine
13 async def main():
14 # report a message
15 print('main coroutine started')
16 # start many tasks
17 started_tasks = [asyncio.create_task(task_coroutine(i)) for i in range(10)]
18 # allow some of the tasks time to start
19 await asyncio.sleep(0.1)
20 # get all tasks
21 tasks = asyncio.all_tasks()
22 # report all tasks
23 for task in tasks:
24 print(f'> {task.get_name()}, {task.get_coro()}')
25 # wait for all tasks to complete
26 for task in started_tasks:
27 await task
28
29 # start the asyncio program
30 asyncio.run(main())
Running the example first creates the main coroutine and uses it to start the asyncio
program.
It then creates and schedules 10 tasks that wrap the custom coroutine,
The main() coroutine then blocks for a moment to allow the tasks to begin running.
The tasks start running and each reports a message and then sleeps.
The main() coroutine resumes and gets a list of all tasks in the program.
Finally, it enumerates the list of tasks that were created and awaits each, allowing
them to be completed.
https://superfastpython.com/python-asyncio/ 69/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
This highlights that we can get a set of all tasks in an asyncio program that includes
both the tasks that were created as well as the task that represents the entry point
into the program.
You can learn more about getting all tasks. in the tutorial:
These coroutines can be created in a group and stored, then executed all together at
the same time.
https://superfastpython.com/python-asyncio/ 70/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
Once grouped, the awaitables can be executed concurrently, awaited, and canceled.
It is a helpful utility function for both grouping and executing multiple coroutines or
multiple tasks.
For example:
1 ...
2 # run a collection of awaitables
3 results = await asyncio.gather(coro1(), asyncio.create_task(coro2()))
We may use the asyncio.gather() function in situations where we may create many
tasks or coroutines up-front and then wish to execute them all at once and wait for
them all to complete before continuing on.
This is a likely situation where the result is required from many like-tasks, e.g. same
task or coroutine with different data.
The awaitables can be executed concurrently, results returned, and the main
program can resume by making use of the results on which it is dependent.
The gather() function is more powerful than simply waiting for tasks to complete.
https://superfastpython.com/python-asyncio/ 71/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
This allows:
Executing and waiting for all awaitables in the group to be done via an await
expression.
Getting results from all grouped awaitables to be retrieved later via the result()
method.
The group of awaitables to be canceled via the cancel() method.
Checking if all awaitables in the group are done via the done() method.
Executing callback functions only when all tasks in the group are done.
And more.
Multiple tasks
Multiple coroutines
Mixture of tasks and coroutines
For example:
1 ...
2 # execute multiple coroutines
3 asyncio.gather(coro1(), coro2())
If Task objects are provided to gather(), they will already be running because Tasks
are scheduled as part of being created.
https://superfastpython.com/python-asyncio/ 72/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
We cannot create a list or collection of awaitables and provide it to gather, as this will
result in an error.
For example:
1 ...
2 # cannot provide a list of awaitables directly
3 asyncio.gather([coro1(), coro2()])
For example:
1 ...
2 # gather with an unpacked list of awaitables
3 asyncio.gather(*[coro1(), coro2()])
For example:
1 ...
2 # get a future that represents multiple awaitables
3 group = asyncio.gather(coro1(), coro2())
Once the Future object is created it is scheduled automatically within the event loop.
The awaitable represents the group, and all awaitables in the group will execute as
soon as they are able.
https://superfastpython.com/python-asyncio/ 73/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
This means that if the caller did nothing else, the scheduled group of awaitables will
run (assuming the caller suspends).
It also means that you do not have to await the Future that is returned from
gather().
For example:
1 ...
2 # get a future that represents multiple awaitables
3 group = asyncio.gather(coro1(), coro2())
4 # suspend and wait a while, the group may be executing..
5 await asyncio.sleep(10)
The returned Future object can be awaited which will wait for all awaitables in the
group to be done.
For example:
1 ...
2 # run the group of awaitables
3 await group
Awaiting the Future returned from gather() will return a list of return values from the
awaitables.
If the awaitables do not return a value, then this list will contain the default “None”
return value.
For example:
1 ...
2 # run the group of awaitables and get return values
3 results = await group
For example:
1 ...
2 # run tasks and get results on one line
3 results = await asyncio.gather(coro1(), coro2())
https://superfastpython.com/python-asyncio/ 74/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
This allows a program to prepare the tasks that are to be executed concurrently and
then trigger their concurrent execution all at once and wait for them to complete.
We can collect many coroutines together into a list either manually or using a list
comprehension.
For example:
1 ...
2 # create many coroutines
3 coros = [task_coro(i) for i in range(10)]
The list of coroutines cannot be provided directly to the gather() function as this will
result in an error.
This can be achieved by unwrapping the list into separate expressions and passing
them to the gather() function. The star operator (*) will perform this operation for us.
For example:
1 ...
2 # run the tasks
3 await asyncio.gather(*coros)
https://superfastpython.com/python-asyncio/ 75/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
1 # SuperFastPython.com
2 # example of gather for many coroutines in a list
3 import asyncio
4
5 # coroutine used for a task
6 async def task_coro(value):
7 # report a message
8 print(f'>task {value} executing')
9 # sleep for a moment
10 await asyncio.sleep(1)
11
12 # coroutine used for the entry point
13 async def main():
14 # report a message
15 print('main starting')
16 # create many coroutines
17 coros = [task_coro(i) for i in range(10)]
18 # run the tasks
19 await asyncio.gather(*coros)
20 # report a message
21 print('main done')
22
23 # start the asyncio program
24 asyncio.run(main())
Running the example executes the main() coroutine as the entry point to the
program.
The main() coroutine then creates a list of 10 coroutine objects using a list
comprehension.
This list is then provided to the gather() function and unpacked into 10 separate
expressions using the star operator.
The main() coroutine then awaits the Future object returned from the call to gather(),
suspending and waiting for all scheduled coroutines to complete their execution.
The coroutines run as soon as they are able, reporting their unique messages and
sleeping before terminating.
Only after all coroutines in the group are complete does the main() coroutine resume
and report its final message.
This highlights how we might prepare a collection of coroutines and provide them as
separate expressions to the gather() function.
https://superfastpython.com/python-asyncio/ 76/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
1 main starting
2 >task 0 executing
3 >task 1 executing
4 >task 2 executing
5 >task 3 executing
6 >task 4 executing
7 >task 5 executing
8 >task 6 executing
9 >task 7 executing
10 >task 8 executing
11 >task 9 executing
12 main done
You can learn more about how to use the gather() function in the tutorial:
Different conditions can be waited for, such as all tasks to complete, the first task to
complete, and the first task to fail with an exception.
What is asyncio.wait()
The asyncio.wait() function (https://docs.python.org/3/library/asyncio-
task.html#asyncio.wait) can be used to wait for a collection of asyncio tasks to
complete.
Recall that an asyncio task is an instance of the asyncio.Task class that wraps a
coroutine. It allows a coroutine to be scheduled and executed independently, and the
Task instance provides a handle on the task for querying status and getting results.
https://superfastpython.com/python-asyncio/ 77/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
The call to wait can be configured to wait for different conditions, such as all tasks
being completed, the first task completed and the first task failing with an error.
This could be a list, dict, or set of task objects that we have created, such as via calls
to the asyncio.create_task() function in a list comprehension.
For example:
1 ...
2 # create many tasks
3 tasks = [asyncio.create_task(task_coro(i)) for i in range(10)]
The asyncio.wait() will not return until some condition on the collection of tasks is
met.
The wait() function returns a tuple of two sets. The first set contains all task objects
that meet the condition, and the second contains all other task objects that do not yet
meet the condition.
These sets are referred to as the “done” set and the “pending” set.
For example:
https://superfastpython.com/python-asyncio/ 78/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
1 ...
2 # wait for all tasks to complete
3 done, pending = await asyncio.wait(tasks)
We can then await this coroutine which will return the tuple of sets.
For example:
1 ...
2 # create the wait coroutine
3 wait_coro = asyncio.wait(tasks)
4 # await the wait coroutine
5 tuple = await wait_coro
The condition waited for can be specified by the “return_when” argument which is
set to asyncio.ALL_COMPLETED by default.
For example:
1 ...
2 # wait for all tasks to complete
3 done, pending = await asyncio.wait(tasks, return_when=asyncio.ALL_COMPLETED)
For example:
1 ...
2 # wait for the first task to be completed
3 done, pending = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
When the first task is complete and returned in the done set, the remaining tasks are
not canceled and continue to execute concurrently.
We can wait for the first task to fail with an exception by setting return_when to
FIRST_EXCEPTION.
For example:
1 ...
2 # wait for the first task to fail
3 done, pending = await asyncio.wait(tasks, return_when=asyncio.FIRST_EXCEPTION)
https://superfastpython.com/python-asyncio/ 79/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
In this case, the done set will contain the first task that failed with an exception. If no
task fails with an exception, the done set will contain all tasks and wait() will return
only after all tasks are completed.
We can specify how long we are willing to wait for the given condition via a “timeout”
argument in seconds.
If the timeout expires before the condition is met, the tuple of tasks is returned with
whatever subset of tasks do meet the condition at that time, e.g. the subset of tasks
that are completed if waiting for all tasks to complete.
For example:
1 ...
2 # wait for all tasks to complete with a timeout
3 done, pending = await asyncio.wait(tasks, timeout=3)
If the timeout is reached before the condition is met, an exception is not raised and
the remaining tasks are not canceled.
Now that we know how to use the asyncio.wait() function, let’s look at some worked
examples.
In this example, we will define a simple task coroutine that generates a random value,
sleeps for a fraction of a second, then reports a message with the generated value.
The main coroutine will then create many tasks in a list comprehension with the
coroutine and then wait for all tasks to be completed.
https://superfastpython.com/python-asyncio/ 80/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
1 # SuperFastPython.com
2 # example of waiting for all tasks to complete
3 from random import random
4 import asyncio
5
6 # coroutine to execute in a new task
7 async def task_coro(arg):
8 # generate a random value between 0 and 1
9 value = random()
10 # block for a moment
11 await asyncio.sleep(value)
12 # report the value
13 print(f'>task {arg} done with {value}')
14
15 # main coroutine
16 async def main():
17 # create many tasks
18 tasks = [asyncio.create_task(task_coro(i)) for i in range(10)]
19 # wait for all tasks to complete
20 done,pending = await asyncio.wait(tasks)
21 # report results
22 print('All done')
23
24 # start the asyncio program
25 asyncio.run(main())
Running the example first creates the main() coroutine and uses it as the entry point
into the asyncio program.
The main() coroutine then creates a list of ten tasks in a list comprehension, each
providing a unique integer argument from 0 to 9.
The main() coroutine is then suspended and waits for all tasks to complete.
The tasks execute. Each generates a random value, sleeps for a moment, then reports
its generated value.
After all tasks have been completed, the main() coroutine resumes and reports a
final message.
This example highlights how we can use the wait() function to wait for a collection of
tasks to be completed.
Note, that the results will differ each time the program is run given the use of random
numbers.
https://superfastpython.com/python-asyncio/ 81/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
1 >task 5 done with 0.0591009105682192
2 >task 8 done with 0.10453715687017351
3 >task 0 done with 0.15462838864295925
4 >task 6 done with 0.4103492027393125
5 >task 9 done with 0.45567100006991623
6 >task 2 done with 0.6984682905809402
7 >task 7 done with 0.7785363531316224
8 >task 3 done with 0.827386088873161
9 >task 4 done with 0.9481344994700972
10 >task 1 done with 0.9577302665040541
11 All done
You can learn more about the wait() function in the tutorial:
Next, let’s look at how we can manage multiple tasks together with a TaskGroup.
If a task in the group fails with an exception, all tasks in the group are cancelled
automatically.
What is an asyncio.TaskGroup
Python 3.11 introduced the asyncio.TaskGroup task for managing a group of
associated asyncio task.
https://superfastpython.com/python-asyncio/ 82/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
For example:
1 ...
2 # create and issue coroutine as task
3 task = asyncio.create_task(coro())
This creates a new asyncio.Task object and issues it to the asyncio event loop for
execution as soon as it is able.
We can then choose to await the task and wait for it to be completed.
For example:
1 ...
2 # wait for task to complete
3 result = await task
You can learn more about executing coroutines as asyncio.Task objects in the
tutorial:
As we have seen, the asyncio.gather() function is used to create and issue many
coroutines simultaneously as asyncio.Task objects to the event loop, allowing the
caller to treat them all as a group.
The most common usage is to wait for all issued tasks to complete.
For example:
https://superfastpython.com/python-asyncio/ 83/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
1 ...
2 # issue coroutines as tasks and wait for them to complete
3 results = await asyncio.gather(coro1(), coro2(), coro2)
The asyncio.TaskGroup can perform both of these activities and is the preferred
approach.
This means that an instance of the class is created and is used via the “async with”
expression.
For example:
1 ...
2 # create a taskgroup
3 async with asyncio.TaskGroup() as group:
4 # ...
If you are new to the “async with” expression, see the tutorial:
https://superfastpython.com/python-asyncio/ 84/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
This means that exiting the TaskGroup object’s block normally or via an exception
will automatically await until all group tasks are done.
1 ...
2 # create a taskgroup
3 async with asyncio.TaskGroup() as group:
4 # ...
5 # wait for all group tasks are done
You can learn more about asynchronous context managers in the tutorial:
For example:
1 ...
2 # create a taskgroup
3 async with asyncio.TaskGroup() as group:
4 # create and issue a task
5 task = group.create_task(coro())
This will create an asyncio.Task object and issue it to the asyncio event loop for
execution, just like the asyncio.create_task() function, except that the task is
associated with the group.
For example:
https://superfastpython.com/python-asyncio/ 85/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
1 ...
2 # create a taskgroup
3 async with asyncio.TaskGroup() as group:
4 # create and issue a task
5 result = await group.create_task(coro())
The benefit of using the asyncio.TaskGroup is that we can issue multiple tasks in the
group and execute code in between. such as checking results or gathering more data.
As such, the tasks are awaited automatically and nothing additional is required.
For example:
1 ...
2 # create a taskgroup
3 async with asyncio.TaskGroup() as group:
4 # ...
5 # wait for all group tasks are done
If this behavior is not preferred, then we must ensure all tasks are “done” (finished,
canceled, or failed) before exiting the context manager.
This is performed automatically and does not require any additional code.
For example:
https://superfastpython.com/python-asyncio/ 86/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
1 # handle the failure of any tasks in the group
2 try:
3 ...
4 # create a taskgroup
5 async with asyncio.TaskGroup() as group:
6 # create and issue a task
7 task1 = group.create_task(coro1())
8 # create and issue a task
9 task2 = group.create_task(coro2())
10 # create and issue a task
11 task3 = group.create_task(coro3())
12 # wait for all group tasks are done
13 except:
14 # all non-done tasks are cancelled
15 pass
If this behavior is not preferred, then the failure of each task must be managed within
the tasks themselves, e.g. by a try-except block within the coroutine.
Now that we know how to use the asyncio.TaskGroup, let’s look at some worked
examples.
This can be achieved by first defining a suite of different coroutines that represent the
tasks we want to complete.
In this case, we will define 3 coroutines that each report a different message and then
sleep for one second.
https://superfastpython.com/python-asyncio/ 87/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
1 # coroutine task
2 async def task1():
3 # report a message
4 print('Hello from coroutine 1')
5 # sleep to simulate waiting
6 await asyncio.sleep(1)
7
8 # coroutine task
9 async def task2():
10 # report a message
11 print('Hello from coroutine 2')
12 # sleep to simulate waiting
13 await asyncio.sleep(1)
14
15 # coroutine task
16 async def task3():
17 # report a message
18 print('Hello from coroutine 3')
19 # sleep to simulate waiting
20 await asyncio.sleep(1)
Next, we can define a main() coroutine that creates the asyncio.TaskGroup via the
context manager interface.
We can then create and issue each coroutine as a task into the event loop, although
collected together as part of the group.
1 ...
2 # run first task
3 group.create_task(task1())
4 # run second task
5 group.create_task(task2())
6 # run third task
7 group.create_task(task3())
Notice that we don’t need to keep a reference to the asyncio.Task objects as the
asyncio.TaskGroup will keep track of them for us.
Also, notice that we don’t need to await the tasks because when we exit the context
manager block for the asyncio.TaskGroup we will await all tasks in the group.
https://superfastpython.com/python-asyncio/ 88/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
1 # example of asyncio task group
2 import asyncio
3
4 # coroutine task
5 async def task1():
6 # report a message
7 print('Hello from coroutine 1')
8 # sleep to simulate waiting
9 await asyncio.sleep(1)
10
11 # coroutine task
12 async def task2():
13 # report a message
14 print('Hello from coroutine 2')
15 # sleep to simulate waiting
16 await asyncio.sleep(1)
17
18 # coroutine task
19 async def task3():
20 # report a message
21 print('Hello from coroutine 3')
22 # sleep to simulate waiting
23 await asyncio.sleep(1)
24
25 # asyncio entry point
26 async def main():
27 # create task group
28 async with asyncio.TaskGroup() as group:
29 # run first task
30 group.create_task(task1())
31 # run second task
32 group.create_task(task2())
33 # run third task
34 group.create_task(task3())
35 # wait for all tasks to complete...
36 print('Done')
37
38 # entry point
39 asyncio.run(main())
Running the example first executes the main() coroutine, starting a new event loop
for us.
All three coroutines are then created as asyncio.Task objects and issued to the event
loop via the asyncio.TaskGroup.
The context manager block for the asyncio.TaskGroup is exited which automatically
awaits all three tasks.
Once all tasks are completed the main() coroutine reports a final message.
https://superfastpython.com/python-asyncio/ 89/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
1 Hello from coroutine 1
2 Hello from coroutine 2
3 Hello from coroutine 3
4 Done
Next, let’s explore how we might use an asyncio.TaskGroup with tasks that take
arguments and return values.
You can learn more about how to use the TaskGroup in the tutorial:
Next, we will explore how to wait for a single coroutine with a time limit.
If the timeout elapses before the task completes, the task is canceled.
If no timeout is specified, the wait_for() function will wait until the task is completed.
If a timeout is specified and elapses before the task is complete, then the task is
canceled.
https://superfastpython.com/python-asyncio/ 90/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
This allows the caller to both set an expectation about how long they are willing to
wait for a task to complete, and to enforce the timeout by canceling the task if the
timeout elapses.
Now that we know what the asyncio.wait_for() function is, let’s look at how to use it.
A timeout must be specified and may be None for no timeout, an integer or floating
point number of seconds.
The wait_for() function returns a coroutine that is not executed until it is explicitly
awaited or scheduled as a task.
For example:
1 ...
2 # wait for a task to complete
3 await asyncio.wait_for(coro, timeout=10)
If the timeout elapses before the task is completed, the task is canceled, and an
asyncio.TimeoutError is raised, which may need to be handled.
https://superfastpython.com/python-asyncio/ 91/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
For example:
1 ...
2 # execute a task with a timeout
3 try:
4 # wait for a task to complete
5 await asyncio.wait_for(coro, timeout=1)
6 except asyncio.TimeoutError:
7 # ...
If the waited-for task fails with an unhandled exception, the exception will be
propagated back to the caller that is awaiting on the wait_for() coroutine, in which
case it may need to be handled.
For example
1 ...
2 # execute a task that may fail
3 try:
4 # wait for a task to complete
5 await asyncio.wait_for(coro, timeout=1)
6 except asyncio.TimeoutError:
7 # ...
8 except Exception:
9 # ...
In this example, we execute a coroutine as above, except the caller waits a fixed
timeout of 0.2 seconds or 200 milliseconds.
The task coroutine is modified so that it sleeps for more than one second, ensuring
that the timeout always expires before the task is complete.
https://superfastpython.com/python-asyncio/ 92/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
1 # SuperFastPython.com
2 # example of waiting for a coroutine with a timeout
3 from random import random
4 import asyncio
5
6 # coroutine to execute in a new task
7 async def task_coro(arg):
8 # generate a random value between 0 and 1
9 value = 1 + random()
10 # report message
11 print(f'>task got {value}')
12 # block for a moment
13 await asyncio.sleep(value)
14 # report all done
15 print('>task done')
16
17 # main coroutine
18 async def main():
19 # create a task
20 task = task_coro(1)
21 # execute and wait for the task without a timeout
22 try:
23 await asyncio.wait_for(task, timeout=0.2)
24 except asyncio.TimeoutError:
25 print('Gave up waiting, task canceled')
26
27 # start the asyncio program
28 asyncio.run(main())
Running the example first creates the main() coroutine and uses it as the entry point
into the asyncio program.
The main() coroutine creates the task coroutine. It then calls wait_for() and passes
the task coroutine and sets the timeout to 0.2 seconds.
The main() coroutine resumes after the timeout has elapsed. The wait_for()
coroutine cancels the task_coro() coroutine and the main() coroutine is suspended.
The task_coro() runs again and responds to the request to be terminated. It raises a
TimeoutError exception and terminates.
The main() coroutine resumes and handles the TimeoutError raised by the
task_coro().
This highlights how we can call the wait_for() function with a timeout and to cancel a
task if it is not completed within a timeout.
https://superfastpython.com/python-asyncio/ 93/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
The output from the program will differ each time it is run given the use of random
numbers.
You can learn more about the wait_for() function in the tutorial:
Next, we will explore how we might protect an asyncio task from being canceled.
This means the shielded future can be passed around to tasks that may try to cancel
it and the cancellation request will look like it was successful, except that the Task or
coroutine that is being shielded will continue to run.
https://superfastpython.com/python-asyncio/ 94/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
It may be useful in asyncio programs where some tasks can be canceled, but others,
perhaps with a higher priority cannot.
It may also be useful in programs where some tasks can safely be canceled, such as
those that were designed with asyncio in mind, whereas others cannot be safely
terminated and therefore must be shielded from cancellation.
Now that we know what asyncio.shield() is, let’s look at how to use it.
The Future object can then be awaited directly or passed to another task or
coroutine.
For example:
1 ...
2 # shield a task from cancellation
3 shielded = asyncio.shield(task)
4 # await the shielded task
5 await shielded
For example:
1 ...
2 # cancel a shielded task
3 was_canceld = shielded.cancel()
For example:
1 ...
2 try:
3 # await the shielded task
4 await asyncio.shield(task)
5 except asyncio.CancelledError:
6 # ...
Importantly, the request for cancellation made on the Future object is not
propagated to the inner task.
This means that the request for cancellation is absorbed by the shield.
For example:
1 ...
2 # create a task
3 task = asyncio.create_task(coro())
4 # create a shield
5 shield = asyncio.shield(task)
6 # cancel the shield (does not cancel the task)
7 shield.cancel()
This means that the shield does not need to be awaited for the inner coroutine to
run.
If the task that is being shielded is canceled, the cancellation request will be
propagated up to the shield, which will also be canceled.
For example:
https://superfastpython.com/python-asyncio/ 96/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
1 ...
2 # create a task
3 task = asyncio.create_task(coro())
4 # create a shield
5 shield = asyncio.shield(task)
6 # cancel the task (also cancels the shield)
7 task.cancel()
Now that we know how to use the asyncio.shield() function, let’s look at some worked
examples.
In this example, we define a simple coroutine task that takes an integer argument,
sleeps for a second, then returns the argument. The coroutine can then be created
and scheduled as a Task.
We can define a second coroutine that takes a task, sleeps for a fraction of a second,
then cancels the provided task.
In the main coroutine, we can then shield the first task and pass it to the second task,
then await the shielded task.
The expectation is that the shield will be canceled and leave the inner task intact. The
cancellation will disrupt the main coroutine. We can check the status of the inner task
at the end of the program and we expect it to have been completed normally,
regardless of the request to cancel made on the shield.
https://superfastpython.com/python-asyncio/ 97/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
1 # SuperFastPython.com
2 # example of using asyncio shield to protect a task from cancellation
3 import asyncio
4
5 # define a simple asynchronous
6 async def simple_task(number):
7 # block for a moment
8 await asyncio.sleep(1)
9 # return the argument
10 return number
11
12 # cancel the given task after a moment
13 async def cancel_task(task):
14 # block for a moment
15 await asyncio.sleep(0.2)
16 # cancel the task
17 was_cancelled = task.cancel()
18 print(f'cancelled: {was_cancelled}')
19
20 # define a simple coroutine
21 async def main():
22 # create the coroutine
23 coro = simple_task(1)
24 # create a task
25 task = asyncio.create_task(coro)
26 # created the shielded task
27 shielded = asyncio.shield(task)
28 # create the task to cancel the previous task
29 asyncio.create_task(cancel_task(shielded))
30 # handle cancellation
31 try:
32 # await the shielded task
33 result = await shielded
34 # report the result
35 print(f'>got: {result}')
36 except asyncio.CancelledError:
37 print('shielded was cancelled')
38 # wait a moment
39 await asyncio.sleep(1)
40 # report the details of the tasks
41 print(f'shielded: {shielded}')
42 print(f'task: {task}')
43
44 # start
45 asyncio.run(main())
Running the example first creates the main() coroutine and uses it as the entry point
into the application.
The shielded task is then passed to the cancel_task() coroutine which is wrapped in a
task and scheduled.
The main coroutine then awaits the shielded task, which expects a CancelledError
exception.
https://superfastpython.com/python-asyncio/ 98/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
The task runs for a moment then sleeps. The cancellation task runs for a moment,
sleeps, resumes then cancels the shielded task. The request to cancel reports that it
was successful.
This raises a CancelledError exception in the shielded Future, although not in the
inner task.
Finally, the main() coroutine resumes, and reports the status of the shielded future
and the inner task. We can see that the shielded future is marked as canceled and yet
the inner task is marked as finished normally and provides a return value.
This example highlights how a shield can be used to successfully protect an inner task
from cancellation.
1 cancelled: True
2 shielded was cancelled
3 shielded: <Future cancelled>
4 task: <Task finished name='Task-2' coro=<simple_task() done, defined at ...> result=1>
You can learn more about the shield() function in the tutorial:
Next, we will explore how to run a blocking task from an asyncio program.
If a blocking task is executed in an asyncio program it stops the entire event loop,
preventing any other coroutines from progressing.
https://superfastpython.com/python-asyncio/ 99/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
Making a blocking call directly in an asyncio program will cause the event loop to stop
while the blocking call is executing. It will not allow other coroutines to run in the
background.
For example:
1 ...
2 # execute a function in a separate thread
3 await asyncio.to_thread(task)
The task will not begin executing until the returned coroutine is given an opportunity
to run in the event loop.
This is in the low-level asyncio API and first requires access to the event loop, such as
via the asyncio.get_running_loop() function
(https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.get_running_loop).
If None is provided for the executor, then the default executor is used, which is a
ThreadPoolExecutor.
For example:
https://superfastpython.com/python-asyncio/ 101/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
1 ...
2 # get the event loop
3 loop = asyncio.get_running_loop()
4 # execute a function in a separate thread
5 await loop.run_in_executor(None, task)
The caller must manage the executor in this case, shutting it down once the caller is
finished with it.
For example:
1 ...
2 # create a process pool
3 with ProcessPoolExecutor as exe:
4 # get the event loop
5 loop = asyncio.get_running_loop()
6 # execute a function in a separate thread
7 await loop.run_in_executor(exe, task)
8 # process pool is shutdown automatically...
Now that we know how to execute blocking calls in an asyncio program, let’s look at
some worked examples.
In this example, we will define a function that blocks the caller for a few seconds. We
will then execute this function asynchronously in a thread pool from asyncio using
the asyncio.to_thread() function.
Running the example first creates the main() coroutine and runs it as the entry point
into the asyncio program.
The main() coroutine runs and reports a message. It then issues a call to the blocking
function call to the thread pool. This returns a coroutine,
The main() coroutine is free to continue with other activities. In this case, it sleeps for
a moment to allow the scheduled task to start executing. This allows the target
function to be issued to the ThreadPoolExecutor behind the scenes and start
running.
The main() coroutine then suspends and waits for the task to complete.
The blocking function reports a message, sleeps for 2 seconds, then reports a final
message.
https://superfastpython.com/python-asyncio/ 103/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
This highlights how we can execute a blocking IO-bound task in a separate thread
asynchronously from an asyncio program.
You can learn more about the to_thread() function in the tutorial:
Asynchronous Iterators
Iteration is a basic operation in Python.
Before we take a close look at asynchronous iterators, let’s review classical iterators.
https://superfastpython.com/python-asyncio/ 104/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
Iterators
Specifically, the __iter__() method that returns an instance of the iterator and the
__next__() method that steps the iterator one cycle and returns a value.
An iterator can be stepped using the next() built-in function or traversed using a for
loop.
Many Python objects are iterable, most notable are containers such as lists.
Asynchronous Iterators
Asynchronous iterators were introduced in PEP 492 – Coroutines with async and
await syntax (https://peps.python.org/pep-0492/).
An asynchronous iterator can be traversed using the “async for” expression that will
automatically call anext() each iteration and await the returned awaitable in order to
retrieve the return value.
You may recall that an awaitable is an object that can be waited for, such as a
coroutine or a task.
https://superfastpython.com/python-asyncio/ 106/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
The await for expression allows the caller to traverse an asynchronous iterator of
awaitables and retrieve the result from each.
This is not the same as traversing a collection or list of awaitables (e.g. coroutine
objects), instead, the awaitables returned must be provided using the expected
asynchronous iterator methods.
Internally, the async for loop will automatically resolve or await each awaitable,
scheduling coroutines as needed.
Because it is a for-loop, it assumes, although does not require, that each awaitable
being traversed yields a return value.
The async for loop must be used within a coroutine because internally it will use the
await expression, which can only be used within coroutines.
The async for expression can be used to traverse an asynchronous iterator within a
coroutine.
For example:
1 ...
2 # traverse an asynchronous iterator
3 async for item in async_iterator:
4 print(item)
This does not execute the for-loop in parallel. The asyncio is unable to execute more
than one coroutine at a time within a Python thread.
https://superfastpython.com/python-asyncio/ 107/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
The difference is that the coroutine that executes the for loop will suspend and
internally await for each awaitable.
Behind the scenes, this may require coroutines to be scheduled and awaited, or tasks
to be awaited.
For example:
1 ...
2 # build a list of results
3 results = [item async for item async_iterator]
This would construct a list of return values from the asynchronous iterator.
Next, let’s look at how to define, create and use asynchronous iterators.
https://superfastpython.com/python-asyncio/ 108/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
— ASYNCHRONOUS ITERATORS
(HTTPS://DOCS.PYTHON.ORG/3/REFERENCE/DATAMODEL.HTML#ASYNCHRONOUS -
ITERATORS)
For example:
For example:
https://superfastpython.com/python-asyncio/ 109/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
1 ...
2 # return the next awaitable
3 async def __anext__(self):
4 # check for no further items
5 if self.counter >= 10:
6 raise StopAsyncIteration
7 # increment the counter
8 self.counter += 1
9 # simulate work
10 await asyncio.sleep(1)
11 # return the counter value
12 return self.counter
For example:
1 ...
2 # create the iterator
3 it = AsyncIterator()
One step of the iterator can be traversed using the anext() built-in function
(https://docs.python.org/3/library/functions.html#anext), just like a classical iterator
using the next() function.
For example:
1 ...
2 # get an awaitable for one step of the iterator
3 awaitable = anext(it)
4 # execute the one step of the iterator and get the result
5 result = await awaitable
https://superfastpython.com/python-asyncio/ 110/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
For example:
1 ...
2 # step the async iterator
3 result = await anext(it)
The asynchronous iterator can also be traversed in a loop using the “async for”
expression that will await each iteration of the loop automatically.
For example:
1 ...
2 # traverse an asynchronous iterator
3 async for result in AsyncIterator():
4 print(result)
You can learn more about the “async for” expression in the tutorial:
We may also use an asynchronous list comprehension with the “async for”
expression to collect the results of the iterator.
For example:
1 ...
2 # async list comprehension with async iterator
3 results = [item async for item in AsyncIterator()]
In this example, we will update the previous example to traverse the iterator to
completion using an “async for” loop.
https://superfastpython.com/python-asyncio/ 111/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
This loop will automatically await each awaitable returned from the iterator, retrieve
the returned value, and make it available within the loop body so that in this case it
can be reported.
This is perhaps the most common usage pattern for asynchronous iterators.
1 # SuperFastPython.com
2 # example of an asynchronous iterator with async for loop
3 import asyncio
4
5 # define an asynchronous iterator
6 class AsyncIterator():
7 # constructor, define some state
8 def __init__(self):
9 self.counter = 0
10
11 # create an instance of the iterator
12 def __aiter__(self):
13 return self
14
15 # return the next awaitable
16 async def __anext__(self):
17 # check for no further items
18 if self.counter >= 10:
19 raise StopAsyncIteration
20 # increment the counter
21 self.counter += 1
22 # simulate work
23 await asyncio.sleep(1)
24 # return the counter value
25 return self.counter
26
27 # main coroutine
28 async def main():
29 # loop over async iterator with async for loop
30 async for item in AsyncIterator():
31 print(item)
32
33 # execute the asyncio program
34 asyncio.run(main())
Running the example first creates the main() coroutine and uses it as the entry point
into the asyncio program.
An instance of the asynchronous iterator is created and the loop automatically steps
it using the anext() function to return an awaitable. The loop then awaits the
awaitable and retrieves a value which is made available to the body of the loop where
it is reported.
https://superfastpython.com/python-asyncio/ 112/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
This process is then repeated, suspending the main() coroutine, executing a step of
the iterator and suspending, and resuming the main() coroutine until the iterator is
exhausted.
Once the internal counter of the iterator reaches 10, a StopAsyncIteration is raised.
This does not terminate the program. Instead, it is expected and handled by the
“async for” expression and breaks the loop.
This highlights how an asynchronous iterator can be traversed using an async for
expression.
1 1
2 2
3 3
4 4
5 5
6 6
7 7
8 8
9 9
10 10
Asynchronous Generators
Generators are a fundamental part of Python.
A generator is a function that has at least one “yield” expression. They are functions
that can be suspended and resumed, just like coroutines.
https://superfastpython.com/python-asyncio/ 113/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
Before we dive into the details of asynchronous generators, let’s first review classical
Python generators.
Generators
For example:
1 # define a generator
2 def generator():
3 for i in range(10):
4 yield i
The generator is executed to the yield expression, after which a value is returned.
This suspends the generator at that point. The next time the generator is executed it
is resumed from the point it was resumed and runs until the next yield expression.
https://superfastpython.com/python-asyncio/ 114/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
For example:
1 ...
2 # create the generator
3 gen = generator()
4 # step the generator
5 result = next(gen)
For example:
1 ...
2 # traverse the generator and collect results
3 results = [item for item in generator()]
Asynchronous Generators
Unlike a function generator, the coroutine can schedule and await other coroutines
and tasks.
https://superfastpython.com/python-asyncio/ 115/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
This means that the asynchronous generator iterator implements the __anext__()
method and can be used with the async for expression.
This means that each iteration of the generator is scheduled and executed as
awaitable. The “async for” expression will schedule and execute each iteration of the
generator, suspending the calling coroutine and awaiting the result.
You can learn more about the “async for” expression in the tutorial:
https://superfastpython.com/python-asyncio/ 116/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
This means that the function is defined using the “async def” expression.
For example:
For example:
https://superfastpython.com/python-asyncio/ 117/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
This looks like calling it, but instead creates and returns an iterator object.
For example:
1 ...
2 # create the iterator
3 it = async_generator()
One step of the generator can be traversed using the anext() built-in function
(https://docs.python.org/3/library/functions.html#anext), just like a classical
generator using the next() function.
For example:
1 ...
2 # get an awaitable for one step of the generator
3 awaitable = anext(gen)
4 # execute the one step of the generator and get the result
5 result = await awaitable
For example:
1 ...
2 # step the async generator
3 result = await anext(gen)
The asynchronous generator can also be traversed in a loop using the “async for”
expression that will await each iteration of the loop automatically.
For example:
https://superfastpython.com/python-asyncio/ 118/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
1 ...
2 # traverse an asynchronous generator
3 async for result in async_generator():
4 print(result)
You can learn more about the “async for” expression in the tutorial:
We may also use an asynchronous list comprehension with the “async for”
expression to collect the results of the generator.
For example:
1 ...
2 # async list comprehension with async generator
3 results = [item async for item in async_generator()]
In this example, we will update the previous example to traverse the generator to
completion using an “async for” loop.
This loop will automatically await each awaitable returned from the generator,
retrieve the yielded value, and make it available within the loop body so that in this
case it can be reported.
This is perhaps the most common usage pattern for asynchronous generators.
https://superfastpython.com/python-asyncio/ 119/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
1 # SuperFastPython.com
2 # example of asynchronous generator with async for loop
3 import asyncio
4
5 # define an asynchronous generator
6 async def async_generator():
7 # normal loop
8 for i in range(10):
9 # block to simulate doing work
10 await asyncio.sleep(1)
11 # yield the result
12 yield i
13
14 # main coroutine
15 async def main():
16 # loop over async generator with async for loop
17 async for item in async_generator():
18 print(item)
19
20 # execute the asyncio program
21 asyncio.run(main())
Running the example first creates the main() coroutine and uses it as the entry point
into the asyncio program.
This highlights how an asynchronous generator can be traversed using an async for
expression.
1 0
2 1
3 2
4 3
5 4
6 5
7 6
8 7
9 8
10 9
https://superfastpython.com/python-asyncio/ 120/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
Before we dive into the details of asynchronous context managers, let’s review
classical context managers.
https://superfastpython.com/python-asyncio/ 121/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
Context Manager
A context manager is a Python object that implements the __enter__() and __exit__()
methods.
The __enter__() method defines what happens at the beginning of a block, such
as opening or preparing resources, like a file, socket or thread pool.
The __exit__() method defines what happens when the block is exited, such as
closing a prepared resource.
“ Typical uses of context managers include saving and restoring various kinds
of global state, locking and unlocking resources, closing opened files, etc.
Typically the context manager object is created in the beginning of the “with”
expression and the __enter__() method is called automatically. The body of the
content makes use of the resource via the named context manager object, then the
__aexit__() method is called automatically when the block is exited, normally or via an
exception.
https://superfastpython.com/python-asyncio/ 122/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
For example:
1 ...
2 # open a context manager
3 with ContextManager() as manager:
4 # ...
5 # closed automatically
For example:
1 ...
2 # create the object
3 manager = ContextManager()
4 try:
5 manager.__enter__()
6 # ...
7 finally:
8 manager.__exit__()
They provide a context manager that can be suspended when entering and exiting.
The __aenter__ and __aexit__ methods are defined as coroutines and are awaited by
the caller.
https://superfastpython.com/python-asyncio/ 123/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
You can learn more about the “async with” expression in the tutorial:
As such, asynchronous context managers can only be used within asyncio programs,
such as within calling coroutines.
The “async with” expression is for creating and using asynchronous context
managers.
The “async with” expression is just like the “with” expression used for context
managers, except it allows asynchronous context managers to be used within
coroutines.
In order to better understand “async with“, let’s take a closer look at asynchronous
context managers.
The async with expression allows a coroutine to create and use an asynchronous
version of a context manager.
For example:
1 ...
2 # create and use an asynchronous context manager
3 async with AsyncContextManager() as manager:
4 # ...
https://superfastpython.com/python-asyncio/ 124/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
1 ...
2 # create or enter the async context manager
3 manager = await AsyncContextManager()
4 try:
5 # ...
6 finally:
7 # close or exit the context manager
8 await manager.close()
Notice that we are implementing much the same pattern as a traditional context
manager, except that creating and closing the context manager involve awaiting
coroutines.
This suspends the execution of the current coroutine, schedules a new coroutine and
waits for it to complete.
Importantly, both methods must be defined as coroutines using the “async def” and
therefore must return awaitables.
For example:
https://superfastpython.com/python-asyncio/ 125/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
1 # define an asynchronous context manager
2 class AsyncContextManager:
3 # enter the async context manager
4 async def __aenter__(self):
5 # report a message
6 print('>entering the context manager')
7
8 # exit the async context manager
9 async def __aexit__(self, exc_type, exc, tb):
10 # report a message
11 print('>exiting the context manager')
Because each of the methods are coroutines, they may themselves await coroutines
or tasks.
For example:
This will automatically await the enter and exit coroutines, suspending the calling
coroutine as needed.
For example:
1 ...
2 # use an asynchronous context manager
3 async with AsyncContextManager() as manager:
4 # ...
As such, the “async with” expression and asynchronous context managers more
generally can only be used within asyncio programs, such as within coroutines.
https://superfastpython.com/python-asyncio/ 126/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
Now that we know how to use asynchronous context managers, let’s look at a worked
example.
In this example, we will update the above example to use the context manager in a
normal manner.
We will use an “async with” expression and on one line, create and enter the context
manager. This will automatically await the enter method.
We can then make use of the manager within the inner block. In this case, we will just
report a message.
Exiting the inner block will automatically await the exit method of the context
manager.
Contrasting this example with the previous example shows how much heavy lifting
the “async with” expression does for us in an asyncio program.
https://superfastpython.com/python-asyncio/ 127/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
1 # SuperFastPython.com
2 # example of an asynchronous context manager via async with
3 import asyncio
4
5 # define an asynchronous context manager
6 class AsyncContextManager:
7 # enter the async context manager
8 async def __aenter__(self):
9 # report a message
10 print('>entering the context manager')
11 # block for a moment
12 await asyncio.sleep(0.5)
13
14 # exit the async context manager
15 async def __aexit__(self, exc_type, exc, tb):
16 # report a message
17 print('>exiting the context manager')
18 # block for a moment
19 await asyncio.sleep(0.5)
20
21 # define a simple coroutine
22 async def custom_coroutine():
23 # create and use the asynchronous context manager
24 async with AsyncContextManager() as manager:
25 # report the result
26 print(f'within the manager')
27
28 # start the asyncio program
29 asyncio.run(custom_coroutine())
Running the example first creates the main() coroutine and uses it as the entry point
into the asyncio program.
This expression automatically calls the enter method and awaits the coroutine. A
message is reported and the coroutine blocks for a moment.
The main() coroutine resumes and executes the body of the context manager,
printing a message.
The block is exited and the exit method of the context manager is awaited
automatically, reporting a message and sleeping a moment.
This highlights the normal usage pattern for an asynchronous context manager in an
asyncio program.
https://superfastpython.com/python-asyncio/ 128/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
You can learn more about async context managers in the tutorial:
Asynchronous Comprehensions
Comprehensions, like list and dict comprehensions are one feature of Python when
we think of “pythonic“.
Asyncio supports two types of asynchronous comprehensions, they are the “async
for” comprehension and the “await” comprehension.
https://superfastpython.com/python-asyncio/ 129/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
“ PEP 530 adds support for using async for in list, set, dict comprehensions
and generator expressions
Comprehensions
Comprehensions allow data collections like lists, dicts, and sets to be created in a
concise way.
A list comprehension allows a list to be created from a for expression within the new
list expression.
For example:
1 ...
2 # create a list using a list comprehension
3 result = [a*2 for a in range(100)]
For example:
https://superfastpython.com/python-asyncio/ 130/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
1 ...
2 # create a dict using a comprehension
3 result = {a:i for a,i in zip(['a','b','c'],range(3))}
4 # create a set using a comprehension
5 result = {a for a in [1, 2, 3, 2, 3, 1, 5, 4]}
Asynchronous Comprehensions
An asynchronous comprehension allows a list, set, or dict to be created using the
“async for” expression with an asynchronous iterable.
“ We propose to allow using async for inside list, set and dict comprehensions.
— PEP 530 – ASYNCHRONOUS COMPREHENSIONS (HTTPS://PEPS.PYTHON.ORG/PEP-
0530/)
For example:
1 ...
2 # async list comprehension with an async iterator
3 result = [a async for a in aiterable]
This will create and schedule coroutines or tasks as needed and yield their results
into a list.
Recall that the “async for” expression (/asyncio-async-for/) may only be used within
coroutines and tasks.
The “async for” expression allows the caller to traverse an asynchronous iterator of
awaitables and retrieve the result from each.
Internally, the async for loop will automatically resolve or await each awaitable,
scheduling coroutines as needed.
An async generator automatically implements the methods for the async iterator and
may also be used in an asynchronous comprehension.
https://superfastpython.com/python-asyncio/ 131/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
For example:
1 ...
2 # async list comprehension with an async generator
3 result = [a async for a in agenerator]
Await Comprehensions
The “await” expression may also be used within a list, set, or dict comprehension,
referred to as an await comprehension.
This allows a data structure, like a list, to be created by suspending and awaiting a
series of awaitables.
For example:
1 ...
2 # await list compression with a collection of awaitables
3 results = [await a for a in awaitables]
https://superfastpython.com/python-asyncio/ 132/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
Next, we will explore how to run commands using subprocesses from asyncio.
The command will run in a subprocess that we can write to and read from using non-
blocking I/O.
What is asyncio.subprocess.Process
The asyncio.subprocess.Process class (https://docs.python.org/3/library/asyncio-
subprocess.html#asyncio.subprocess.Process) provides a representation of a
subprocess run by asyncio.
https://superfastpython.com/python-asyncio/ 133/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
Now that we know what the asyncio.subprocess.Process class is, let’s look at how
we might use it in our asyncio programs.
There are two ways to execute an external program as a subprocess and acquire a
Process instance, they are:
https://superfastpython.com/python-asyncio/ 134/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
And so on.
https://superfastpython.com/python-asyncio/ 135/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
This means that the capabilities provided by the shell, such as shell variables,
scripting, and wildcards are not available when executing the command.
It also means that executing the command may be more secure as there is no
opportunity for a shell injection
(https://en.wikipedia.org/wiki/Code_injection#Shell_injection).
https://superfastpython.com/python-asyncio/ 136/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
For example:
1 ...
2 # execute a command in a subprocess
3 process = await asyncio.create_subprocess_exec('ls')
For example:
1 ...
2 # execute a command with arguments in a subprocess
3 process = await asyncio.create_subprocess_exec('ls', '-l')
We can wait for the subprocess to finish by awaiting the wait() method.
For example:
1 ...
2 # wait for the subprocess to terminate
3 await process.wait()
We can stop the subprocess directly by calling the terminate() or kill() methods,
which will raise a signal in the subprocess.
For example:
1 ...
2 # terminate the subprocess
3 process.terminate()
The input and output of the command will be handled by stdin, stderr, and stdout.
We can have the asyncio program handle the input or output for the subprocess.
This can be achieved by specifying the input or output stream and specifying a
constant to redirect, such as asyncio.subprocess.PIPE.
https://superfastpython.com/python-asyncio/ 137/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
For example, we can redirect the output of a command to the asyncio program:
1 ...
2 # start a subprocess and redirect output
3 process = await asyncio.create_subprocess_exec('ls', stdout=asyncio.subprocess.PIPE)
We can then read the output of the program via the asyncio.subprocess.Process
instance via the communicate() method.
This method is a coroutine and must be awaited. It is used to both send and receive
data with the subprocess.
For example:
1 ...
2 # read data from the subprocess
3 line = process.communicate()
We can also send data to the subprocess via the communicate() method by setting
the “input” argument in bytes.
For example:
1 ...
2 # start a subprocess and redirect input
3 process = await asyncio.create_subprocess_exec('ls', stdin=asyncio.subprocess.PIPE)
4 # send data to the subprocess
5 process.communicate(input=b'Hello\n')
https://superfastpython.com/python-asyncio/ 138/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
We can interact with the StreamReader or StreamWriter directly via the subprocess
via the stdin, stdout, and stderr attributes.
For example:
1 ...
2 # read a line from the subprocess output stream
3 line = await process.stdout.readline()
Now that we know how to use the create_subprocess_exec() function, let’s look at
some worked examples.
The echo command will report the provided string on standard output directly.
Note, this example assumes you have access to the “echo” command, I’m not sure it
will work on Windows.
1 # SuperFastPython.com
2 # example of executing a command as a subprocess with asyncio
3 import asyncio
4
5 # main coroutine
6 async def main():
7 # start executing a command in a subprocess
8 process = await asyncio.create_subprocess_exec('echo', 'Hello World')
9 # report the details of the subprocess
10 print(f'subprocess: {process}')
11
12 # entry point
13 asyncio.run(main())
Running the example first creates the main() coroutine and executes it as the entry
point into the asyncio program.
https://superfastpython.com/python-asyncio/ 139/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
The main() coroutine suspends while the subprocess is created. A Process instance is
returned.
The main() coroutine resumes and reports the details of the subprocess. The main()
process terminates and the asyncio program terminates.
1 Hello World
2 subprocess: <Process 50249>
The shell is a user interface for the command line, called a command line interpreter
(CLI).
For example, we can redirect the output of one command as input to another
command, such as the contents of the “/etc/services” file into the word count “wc”
command and count the number of lines:
1 cat /etc/services | wc -l
https://superfastpython.com/python-asyncio/ 140/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
‘sh’
‘bash’
‘zsh’
And so on.
The shell is already running, it was used to start the Python program.
You don’t need to do anything special to get or have access to the shell.
This is helpful as it not only allows the command to be executed, but allows the
capabilities of the shell to be used, such as redirection, wildcards and more.
https://superfastpython.com/python-asyncio/ 141/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
“ … the specified command will be executed through the shell. This can be
useful if you are using Python primarily for the enhanced control flow it
offers over most system shells and still want convenient access to other shell
features such as shell pipes, filename wildcards, environment variable
expansion, and expansion of ~ to a user’s home directory.
The command will be executed in a subprocess of the process executing the asyncio
program.
There can be security considerations when executing a command via the shell instead
of directly.
This is because there is at least one level of indirection and interpretation between
the request to execute the command and the command being executed, allowing
possible malicious injection.
https://superfastpython.com/python-asyncio/ 142/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
For example:
1 ...
2 # start a subprocess
3 process = await asyncio.create_subprocess_shell('ls')
https://superfastpython.com/python-asyncio/ 143/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
We can wait for the subprocess to finish by awaiting the wait() method.
For example:
1 ...
2 # wait for the subprocess to terminate
3 await process.wait()
We can stop the subprocess directly by calling the terminate() or kill() methods,
which will raise a signal in the subprocess.
The input and output of the command will be handled by the shell, e.g. stdin, stderr,
and stdout.
We can have the asyncio program handle the input or output for the subprocess.
This can be achieved by specifying the input or output stream and specifying a
constant to redirect, such as asyncio.subprocess.PIPE.
For example, we can redirect the output of a command to the asyncio program:
1 ...
2 # start a subprocess and redirect output
3 process = await asyncio.create_subprocess_shell('ls', stdout=asyncio.subprocess.PIPE)
We can then read the output of the program via the asyncio.subprocess.Process
instance via the communicate() method.
This method is a coroutine and must be awaited. It is used to both send and receive
data with the subprocess.
For example:
1 ...
2 # read data from the subprocess
3 line = process.communicate()
We can also send data to the subprocess via the communicate() method by setting
the “input” argument in bytes.
https://superfastpython.com/python-asyncio/ 144/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
For example:
1 ...
2 # start a subprocess and redirect input
3 process = await asyncio.create_subprocess_shell('ls', stdin=asyncio.subprocess.PIPE)
4 # send data to the subprocess
5 process.communicate(input=b'Hello\n')
We can interact with the StreamReader or StreamWriter directly via the subprocess
via the stdin, stdout, and stderr attributes.
For example:
1 ...
2 # read a line from the subprocess output stream
3 line = await process.stdout.readline()
Now that we know how to use the create_subprocess_shell() function, let’s look at
some worked examples.
We can explore how to run a command in a subprocess from asyncio using the shell.
The echo command will report the provided string on standard output directly.
Note, this example assumes you have access to the “echo” command, I’m not sure it
will work on Windows.
1 # SuperFastPython.com
2 # example of executing a shell command as a subprocess with asyncio
3 import asyncio
4
5 # main coroutine
6 async def main():
7 # start executing a shell command in a subprocess
8 process = await asyncio.create_subprocess_shell('echo Hello World')
9 # report the details of the subprocess
10 print(f'subprocess: {process}')
11
12 # entry point
13 asyncio.run(main())
Running the example first creates the main() coroutine and executes it as the entry
point into the asyncio program.
The main() coroutine suspends while the subprocess is created. A Process instance is
returned.
The main() coroutine resumes and reports the details of the subprocess. The main()
process terminates and the asyncio program terminates.
This highlights how we can execute a command using the shell from an asyncio
program.
https://superfastpython.com/python-asyncio/ 146/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
Non-Blocking Streams
A major benefit of asyncio is the ability to use non-blocking streams.
Asyncio Streams
Asyncio provides non-blocking I/O socket programming.
Sockets can be opened that provide access to a stream writer and a stream writer.
Data can then be written and read from the stream using coroutines, suspending
when appropriate.
The asyncio streams capability is low-level meaning that any protocols required must
be implemented manually.
The streams can also be used to create a server to handle requests using a standard
protocol, or to develop your own application-specific protocol.
Now that we know what asyncio streams are, let’s look at how to use them.
This is a coroutine that must be awaited and will return once the socket connection is
open.
The function returns a StreamReader and StreamWriter object for interacting with
the socket.
For example:
1 ...
2 # open a connection
3 reader, writer = await asyncio.open_connection(...)
https://superfastpython.com/python-asyncio/ 148/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
The two required arguments are the host and the port.
The host is a string that specifies the server to connect to, such as a domain name or
an IP address.
The port is the socket port number, such as 80 for HTTP servers, 443 for HTTPS
servers, 23 for SMTP and so on.
For example:
1 ...
2 # open a connection to an http server
3 reader, writer = await asyncio.open_connection('www.google.com', 80)
For example:
1 ...
2 # open a connection to an https server
3 reader, writer = await asyncio.open_connection('www.google.com', 443, ssl=True)
“ Create a TCP server (socket type SOCK_STREAM) listening on port of the host
address.
https://superfastpython.com/python-asyncio/ 149/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
For example:
1 ...
2 # start a tcp server
3 server = await asyncio.start_server(...)
The three required arguments are the callback function, the host, and the port.
The callback function is a custom function specified by name that will be called each
time a client connects to the server.
The host is the domain name or IP address that clients will specify to connect. The
port is the socket port number on which to receive connections, such as 21 for FTP or
80 for HTTP.
For example:
1 # handle connections
2 async def handler(reader, writer):
3 # ...
4
5 ...
6 # start a server to receive http connections
7 server = await asyncio.start_server(handler, '127.0.0.1', 80)
https://superfastpython.com/python-asyncio/ 150/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
“ Represents a writer object that provides APIs to write data to the IO stream.
— ASYNCIO STREAMS (HTTPS://DOCS.PYTHON.ORG/3/LIBRARY/ASYNCIO-
STREAM.HTML)
Byte data can be written to the socket using the write() method
(https://docs.python.org/3/library/asyncio-stream.html#asyncio.StreamWriter.write).
“ The method attempts to write the data to the underlying socket immediately.
If that fails, the data is queued in an internal write buffer until it can be sent.
For example:
1 ...
2 # write byte data
3 writer.write(byte_data)
Alternatively, multiple “lines” of byte data organized into a list or iterable can be
written using the writelines() method (https://docs.python.org/3/library/asyncio-
stream.html#asyncio.StreamWriter.writelines).
For example:
1 ...
2 # write lines of byte data
3 writer.writelines(byte_lines)
https://superfastpython.com/python-asyncio/ 151/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
Neither method for writing data blocks or suspends the calling coroutine.
After writing byte data it is a good idea to drain the socket via the drain() method
(https://docs.python.org/3/library/asyncio-stream.html#asyncio.StreamWriter.drain).
This is a coroutine and will suspend the caller until the bytes have been transmitted
and the socket is ready.
For example:
1 ...
2 # write byte data
3 writer.write(byte_data)
4 # wait for data to be transmitted
5 await writer.drain()
“ Represents a reader object that provides APIs to read data from the IO
stream.
Data is read in byte format, therefore strings may need to be encoded before being
used.
https://superfastpython.com/python-asyncio/ 152/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
An arbitrary number of bytes can be read via the read() method, which will read until
the end of file (EOF).
1 ...
2 # read byte data
3 byte_data = await reader.read()
Additionally, the number of bytes to read can be specified via the “n” argument.
“ Read up to n bytes. If n is not provided, or set to -1, read until EOF and
return all read bytes.
This may be helpful if you know the number of bytes expected from the next
response.
For example:
1 ...
2 # read byte data
3 byte_data = await reader.read(n=100)
This will return bytes until a new line character ‘\n’ is encountered, or EOF.
https://superfastpython.com/python-asyncio/ 153/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
“ Read one line, where “line” is a sequence of bytes ending with \n. If EOF is
received and \n was not found, the method returns partially read data. If
EOF is received and the internal buffer is empty, return an empty bytes
object.
This is helpful when reading standard protocols that operate with lines of text.
1 ...
2 # read a line data
3 byte_line = await reader.readline()
The close() method can be called which will close the socket.
For example:
1 ...
2 # close the socket
3 writer.close()
https://superfastpython.com/python-asyncio/ 154/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
Although the close() method does not block, we can wait for the socket to close
completely before continuing on.
“ Wait until the stream is closed. Should be called after close() to wait until the
underlying connection is closed.
For example:
1 ...
2 # close the socket
3 writer.close()
4 # wait for the socket to close
5 await writer.wait_closed()
We can check if the socket has been closed or is in the process of being closed via the
is_closing() method.
For example:
1 ...
2 # check if the socket is closed or closing
3 if writer.is_closing():
4 # ...
Now that we know how to use asyncio streams, let’s look at a worked example.
https://superfastpython.com/python-asyncio/ 155/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
We can then use asyncio to query the status of many websites concurrently, and even
report the results dynamically.
1. Open a connection
2. Write a request
3. Read a response
4. Close the connection
Among many arguments, the function takes the string hostname and integer port
number
For example:
1 ...
2 # open a socket connection
3 reader, writer = await asyncio.open_connection('www.google.com', 80)
We can also open an SSL connection using the ssl=True argument. This can be used
to open an HTTPS connection on port 443.
For example:
1 ...
2 # open a socket connection
3 reader, writer = await asyncio.open_connection('www.google.com', 443)
1 GET / HTTP/1.1
2 Host: www.google.com
Importantly, there must be a carriage return and a line feed (\r\n) at the end of each
line, and an empty line at the end.
1 'GET / HTTP/1.1\r\n'
2 'Host: www.google.com\r\n'
3 '\r\n'
You can learn more about HTTP v1.1 request messages here:
https://superfastpython.com/python-asyncio/ 157/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
This string must be encoded as bytes before being written to the StreamWriter
(https://docs.python.org/3/library/asyncio-stream.html#streamwriter).
For example:
1 ...
2 # encode string as bytes
3 byte_data = string.encode()
The bytes can then be written to the socket via the StreamWriter via the write()
method (https://docs.python.org/3/library/asyncio-
stream.html#asyncio.StreamWriter.write).
For example:
1 ...
2 # write query to socket
3 writer.write(byte_data)
After writing the request, it is a good idea to wait for the byte data to be sent and for
the socket to be ready.
For example:
https://superfastpython.com/python-asyncio/ 158/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
1 ...
2 # wait for the socket to be ready.
3 await writer.drain()
The response can be read using the read() method which will read a chunk of bytes,
or the readline() method which will read one line of bytes.
We might prefer the readline() method because we are using the text-based HTTP
protocol which sends HTML data one line at a time.
For example:
1 ...
2 # read one line of response
3 line_bytes = await reader.readline()
The header has information about whether the request was successful and what type
of file will be sent, and the body contains the content of the file, such as an HTML
webpage.
The first line of the HTTP header contains the HTTP status for the requested page on
the server.
https://superfastpython.com/python-asyncio/ 159/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
For example:
1 ...
2 # decode bytes into a string
3 line_data = line_bytes.decode()
For example:
1 ...
2 # close the connection
3 writer.close()
This does not block and may not close the socket immediately.
Now that we know how to make HTTP requests and read responses using asyncio,
let’s look at some worked examples of checking web page statuses.
https://superfastpython.com/python-asyncio/ 160/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
In this example, we will first develop a coroutine that will check the status of a given
URL. We will then call this coroutine once for each of the top 10 websites
(https://en.wikipedia.org/wiki/List_of_most_visited_websites).
Firstly, we can define a coroutine that will take a URL string and return the HTTP
status.
We require the hostname and file path when making the HTTP request. We also need
to know the URL scheme (HTTP or HTTPS) in order to determine whether SSL is
required nor not.
1 ...
2 # split the url into components
3 url_parsed = urlsplit(url)
We can then open the HTTP connection based on the URL scheme and use the URL
hostname.
1 ...
2 # open the connection
3 if url_parsed.scheme == 'https':
4 reader, writer = await asyncio.open_connection(url_parsed.hostname, 443, ssl=True)
5 else:
6 reader, writer = await asyncio.open_connection(url_parsed.hostname, 80)
Next, we can create the HTTP GET request using the hostname and file path and write
the encoded bytes to the socket using the StreamWriter.
https://superfastpython.com/python-asyncio/ 161/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
1 ...
2 # send GET request
3 query = f'GET {url_parsed.path} HTTP/1.1\r\nHost: {url_parsed.hostname}\r\n\r\n'
4 # write query to socket
5 writer.write(query.encode())
6 # wait for the bytes to be written to the socket
7 await writer.drain()
We only require the first line of the response that contains the HTTP status.
1 ...
2 # read the single line response
3 response = await reader.readline()
1 ...
2 # close the connection
3 writer.close()
Finally, we can decode the bytes read from the server, remote trailing white space,
and return the HTTP status.
1 ...
2 # decode and strip white space
3 status = response.decode().strip()
4 # return the response
5 return status
It does not have any error handling, such as the case where the host cannot be
reached or is slow to respond.
https://superfastpython.com/python-asyncio/ 162/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
1 # get the HTTP/S status of a webpage
2 async def get_status(url):
3 # split the url into components
4 url_parsed = urlsplit(url)
5 # open the connection
6 if url_parsed.scheme == 'https':
7 reader, writer = await asyncio.open_connection(url_parsed.hostname, 443, ssl=True)
8 else:
9 reader, writer = await asyncio.open_connection(url_parsed.hostname, 80)
10 # send GET request
11 query = f'GET {url_parsed.path} HTTP/1.1\r\nHost: {url_parsed.hostname}\r\n\r\n'
12 # write query to socket
13 writer.write(query.encode())
14 # wait for the bytes to be written to the socket
15 await writer.drain()
16 # read the single line response
17 response = await reader.readline()
18 # close the connection
19 writer.close()
20 # decode and strip white space
21 status = response.decode().strip()
22 # return the response
23 return status
Next, we can call the get_status() coroutine for multiple web pages or websites we
want to check.
In this case, we will define a list of the top 10 web pages in the world.
1 ...
2 # list of top 10 websites to check
3 sites = ['https://www.google.com/',
4 'https://www.youtube.com/',
5 'https://www.facebook.com/',
6 'https://twitter.com/',
7 'https://www.instagram.com/',
8 'https://www.baidu.com/',
9 'https://www.wikipedia.org/',
10 'https://yandex.ru/',
11 'https://yahoo.com/',
12 'https://www.whatsapp.com/'
13 ]
In this case, we will do so sequentially in a loop, and report the status of each in turn.
1 ...
2 # check the status of all websites
3 for url in sites:
4 # get the status for the url
5 status = await get_status(url)
6 # report the url and its status
7 print(f'{url:30}:\t{status}')
We can do better than sequential when using asyncio, but this provides a good
starting point that we can improve upon later.
https://superfastpython.com/python-asyncio/ 163/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
Tying this together, the main() coroutine queries the status of the top 10 websites.
1 # main coroutine
2 async def main():
3 # list of top 10 websites to check
4 sites = ['https://www.google.com/',
5 'https://www.youtube.com/',
6 'https://www.facebook.com/',
7 'https://twitter.com/',
8 'https://www.instagram.com/',
9 'https://www.baidu.com/',
10 'https://www.wikipedia.org/',
11 'https://yandex.ru/',
12 'https://yahoo.com/',
13 'https://www.whatsapp.com/'
14 ]
15 # check the status of all websites
16 for url in sites:
17 # get the status for the url
18 status = await get_status(url)
19 # report the url and its status
20 print(f'{url:30}:\t{status}')
Finally, we can create the main() coroutine and use it as the entry point to the
asyncio program.
1 ...
2 # run the asyncio program
3 asyncio.run(main())
https://superfastpython.com/python-asyncio/ 164/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
1 # SuperFastPython.com
2 # check the status of many webpages
3 import asyncio
4 from urllib.parse import urlsplit
5
6 # get the HTTP/S status of a webpage
7 async def get_status(url):
8 # split the url into components
9 url_parsed = urlsplit(url)
10 # open the connection
11 if url_parsed.scheme == 'https':
12 reader, writer = await asyncio.open_connection(url_parsed.hostname, 443, ssl=True)
13 else:
14 reader, writer = await asyncio.open_connection(url_parsed.hostname, 80)
15 # send GET request
16 query = f'GET {url_parsed.path} HTTP/1.1\r\nHost: {url_parsed.hostname}\r\n\r\n'
17 # write query to socket
18 writer.write(query.encode())
19 # wait for the bytes to be written to the socket
20 await writer.drain()
21 # read the single line response
22 response = await reader.readline()
23 # close the connection
24 writer.close()
25 # decode and strip white space
26 status = response.decode().strip()
27 # return the response
28 return status
29
30 # main coroutine
31 async def main():
32 # list of top 10 websites to check
33 sites = ['https://www.google.com/',
34 'https://www.youtube.com/',
35 'https://www.facebook.com/',
36 'https://twitter.com/',
37 'https://www.instagram.com/',
38 'https://www.baidu.com/',
39 'https://www.wikipedia.org/',
40 'https://yandex.ru/',
41 'https://yahoo.com/',
42 'https://www.whatsapp.com/'
43 ]
44 # check the status of all websites
45 for url in sites:
46 # get the status for the url
47 status = await get_status(url)
48 # report the url and its status
49 print(f'{url:30}:\t{status}')
50
51 # run the asyncio program
52 asyncio.run(main())
Running the example first creates the main() coroutine and uses it as the entry point
into the program.
The list of websites is then traversed sequentially. The main() coroutine suspends
and calls the get_status() coroutine to query the status of one website.
https://superfastpython.com/python-asyncio/ 165/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
The get_status() coroutine runs, parses the URL, and opens a connection. It
constructs an HTTP GET query and writes it to the host. A response is read, decoded,
and returned.
The main() coroutine resumes and reports the HTTP status of the URL.
The program takes about 5.6 seconds to complete, or about half a second per URL on
average.
This highlights how we can use asyncio to query the HTTP status of webpages.
Nevertheless, it does not take full advantage of the asyncio to execute tasks
concurrently.
Next, let’s look at how we might update the example to execute the coroutines
concurrently.
https://superfastpython.com/python-asyncio/ 166/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
This function takes one or more coroutines, suspends executing the provided
coroutines, and returns the results from each as an iterable. We can then traverse the
list of URLs and iterable of return values from the coroutines and report results.
1 ...
2 # create all coroutine requests
3 coros = [get_status(url) for url in sites]
Next, we can execute the coroutines and get the iterable of results using
asyncio.gather().
Note that we cannot provide the list of coroutines directly, but instead must unpack
the list into separate expressions that are provided as positional arguments to the
function.
1 ...
2 # execute all coroutines and wait
3 results = await asyncio.gather(*coros)
This will execute all of the coroutines concurrently and retrieve their results.
We can then traverse the list of URLs and returned status and report each in turn.
1 ...
2 # process all results
3 for url, status in zip(sites, results):
4 # report status
5 print(f'{url:30}:\t{status}')
https://superfastpython.com/python-asyncio/ 167/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
1 # SuperFastPython.com
2 # check the status of many webpages
3 import asyncio
4 from urllib.parse import urlsplit
5
6 # get the HTTP/S status of a webpage
7 async def get_status(url):
8 # split the url into components
9 url_parsed = urlsplit(url)
10 # open the connection
11 if url_parsed.scheme == 'https':
12 reader, writer = await asyncio.open_connection(url_parsed.hostname, 443, ssl=True)
13 else:
14 reader, writer = await asyncio.open_connection(url_parsed.hostname, 80)
15 # send GET request
16 query = f'GET {url_parsed.path} HTTP/1.1\r\nHost: {url_parsed.hostname}\r\n\r\n'
17 # write query to socket
18 writer.write(query.encode())
19 # wait for the bytes to be written to the socket
20 await writer.drain()
21 # read the single line response
22 response = await reader.readline()
23 # close the connection
24 writer.close()
25 # decode and strip white space
26 status = response.decode().strip()
27 # return the response
28 return status
29
30 # main coroutine
31 async def main():
32 # list of top 10 websites to check
33 sites = ['https://www.google.com/',
34 'https://www.youtube.com/',
35 'https://www.facebook.com/',
36 'https://twitter.com/',
37 'https://www.instagram.com/',
38 'https://www.baidu.com/',
39 'https://www.wikipedia.org/',
40 'https://yandex.ru/',
41 'https://yahoo.com/',
42 'https://www.whatsapp.com/'
43 ]
44 # create all coroutine requests
45 coros = [get_status(url) for url in sites]
46 # execute all coroutines and wait
47 results = await asyncio.gather(*coros)
48 # process all results
49 for url, status in zip(sites, results):
50 # report status
51 print(f'{url:30}:\t{status}')
52
53 # run the asyncio program
54 asyncio.run(main())
The asyncio.gather() function is then called, passing the coroutines and suspending
the main() coroutine until they are all complete.
https://superfastpython.com/python-asyncio/ 168/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
The coroutines execute, querying each website concurrently and returning their
status.
The main() coroutine resumes and receives an iterable of status values. This iterable
along with the list of URLs is then traversed using the zip() built-in function and the
statuses are reported.
It is also faster than the sequential version above, completing in about 1.4 seconds on
my system.
Next, let’s explore common errors when getting started with asyncio.
https://superfastpython.com/python-asyncio/ 169/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
For example, we can define a coroutine using the “async def” expression:
1 # custom coroutine
2 async def custom_coro():
3 print('hi there')
The beginner will then attempt to call this coroutine like a function and expect the
print message to be reported.
For example:
1 ...
2 # error attempt at calling a coroutine like a function
3 custom_coro()
Calling a coroutine like a function will not execute the body of the coroutine.
This object can then be awaited within the asyncio runtime, e.g. the event loop.
We can start the event loop to run the coroutine using the asyncio.run() function.
For example:
1 ...
2 # run a coroutine
3 asyncio.run(custom_coro())
Alternatively, we can suspend the current coroutine and schedule the other coroutine
using the “await” expression.
For example:
https://superfastpython.com/python-asyncio/ 170/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
1 ...
2 # schedule a coroutine
3 await custom_coro()
This will happen if you create a coroutine object but do not schedule it for execution
within the asyncio event loop.
For example, you may attempt to call a coroutine from a regular Python program:
1 ...
2 # attempt to call the coroutine
3 custom_coro()
For example:
1 ...
2 # create a coroutine object
3 coro = custom_coro()
If you do not allow this coroutine to run, you will get a runtime error.
You can let the coroutine run, as we saw in the previous section, by starting the
asyncio event loop and passing it the coroutine object.
For example:
https://superfastpython.com/python-asyncio/ 171/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
1 ...
2 # create a coroutine object
3 coro = custom_coro()
4 # run a coroutine
5 asyncio.run(coro)
1 ...
2 # run a coroutine
3 asyncio.run(custom_coro())
If you get this error within an asyncio program, it is because you have created a
coroutine and have not scheduled it for execution.
For example:
1 ...
2 # create a coroutine object
3 coro = custom_coro()
4 # suspend and allow the other coroutine to run
5 await coro
For example:
1 ...
2 # create a coroutine object
3 coro = custom_coro()
4 # schedule the coro to run as a task interdependently
5 task = asyncio.create_task(coro)
Using the wrong API makes things more verbose (e.g. more code), more difficult, and
way less understandable.
The lower-level API provides the foundation for the high-level API and includes the
internals of the event loop, transport protocols, policies, and more.
We may dip into the low-level API to achieve specific outcomes on occasion.
If you start getting a handle on the event loop or use a “loop” variable to do things,
you are doing it wrong.
https://superfastpython.com/python-asyncio/ 173/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
Drive asyncio via the high-level API for a while. Develop some programs. Get
comfortable with asynchronous programming and running coroutines at will.
The main coroutine, the entry point for the asyncio program, can then carry on with
other activities.
If the main coroutine exits, then the asyncio program will terminate.
The program will terminate even if there are one or many coroutines running
independently as tasks.
You may issue many tasks and then allow the main coroutine to resume, expecting all
issued tasks to complete in their own time.
Instead, if the main coroutine has nothing else to do, it should wait on the remaining
tasks.
https://superfastpython.com/python-asyncio/ 174/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
This can be achieved by first getting a set of all running tasks via the
asyncio.all_tasks() function, removing itself from this set, then waiting on the
remaining tasks via the asyncio.wait() function.
For example:
1 ...
2 # get a set of all running tasks
3 all_tasks = asyncio.all_tasks()
4 # get the current tasks
5 current_task = asyncio.current_task()
6 # remove the current task from the list of all tasks
7 all_tasks.remove(current_task)
8 # suspend until all tasks are completed
9 await asyncio.wait(all_tasks)
A race condition involves two or more units of concurrency executing the same
critical section at the same time and leaving a resource or data in an inconsistent or
unexpected state. This can lead to data corruption and data loss.
A deadlock is when a unit of concurrency waits for a condition that can never occur,
such as for a resource to become available.
Many Python developers believe these problems are not possible with coroutines in
asyncio.
The reason being that only one coroutine can run within the event loop at any one
time.
https://superfastpython.com/python-asyncio/ 175/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
The problem is, coroutines can suspend and resume and may do so while using a
shared resource or shared variable.
Without protecting critical sections, race conditions can occur in asyncio programs.
The cancel() method returns True if the task was canceled, or False otherwise.
For example:
1 ...
2 # cancel the task
3 was_cancelled = task.cancel()
If the task is already done, it cannot be canceled and the cancel() method will return
False and the task will not have the status of canceled.
https://superfastpython.com/python-asyncio/ 176/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
The next time the task is given an opportunity to run, it will raise a CancelledError
exception.
If the CancelledError exception is not handled within the wrapped coroutine, the
task will be canceled.
The cancel() method can also take a message argument which will be used in the
content of the CancelledError.
In this example, we define a task coroutine that reports a message and then blocks
for a moment.
We then define the main coroutine that is used as the entry point into the asyncio
program. It reports a message, creates and schedules the task, then waits a moment.
The main coroutine then resumes and cancels the task while it is running. It waits a
moment more to allow the task to respond to the request to cancel. The main
coroutine then reports whether the request to cancel the task was successful.
The main coroutine then reports whether the status of the task is canceled before
closing the program.
https://superfastpython.com/python-asyncio/ 177/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
1 # SuperFastPython.com
2 # example of canceling a running task
3 import asyncio
4
5 # define a coroutine for a task
6 async def task_coroutine():
7 # report a message
8 print('executing the task')
9 # block for a moment
10 await asyncio.sleep(1)
11
12 # custom coroutine
13 async def main():
14 # report a message
15 print('main coroutine started')
16 # create and schedule the task
17 task = asyncio.create_task(task_coroutine())
18 # wait a moment
19 await asyncio.sleep(0.1)
20 # cancel the task
21 was_cancelled = task.cancel()
22 # report whether the cancel request was successful
23 print(f'was canceled: {was_cancelled}')
24 # wait a moment
25 await asyncio.sleep(0.1)
26 # check the status of the task
27 print(f'canceled: {task.cancelled()}')
28 # report a final message
29 print('main coroutine done')
30
31 # start the asyncio program
32 asyncio.run(main())
Running the example starts the asyncio event loop and executes the main()
coroutine.
The main() coroutine reports a message, then creates and schedules the task
coroutine.
It then suspends and awaits a moment to allow the task coroutine to begin running.
The main() coroutine resumes and cancels the task. It reports that the request to
cancel the task was successful.
It then sleeps for a moment to allow the task to respond to the request to be
canceled.
https://superfastpython.com/python-asyncio/ 178/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
The main() coroutine resumes and reports whether the task has the status of
canceled. In this case, it does.
For example:
1 ...
2 # wait for the task to finish
3 await task
For example:
1 ...
2 # create and wait for the task to finish
3 await asyncio.create_task(custom_coro())
For example:
https://superfastpython.com/python-asyncio/ 179/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
1 # coroutine that returns a value
2 async def other_coro():
3 return 100
Awaiting the other coroutine will suspend the calling coroutine and schedule the
other coroutine for execution. Once the other coroutine has been completed, the
calling coroutine will resume. The return value will be passed from the other
coroutine to the caller.
For example:
1 ...
2 # execute coroutine and retrieve return value
3 value = await other_coro()
This is helpful for independently executing the coroutine without having the current
coroutine await it.
For example:
1 ...
2 # wrap coroutine in a task and schedule it for execution
3 task = asyncio.create_task(other_coro())
You can learn more about how to create tasks in the tutorial:
There are two ways to retrieve the return value from an asyncio.Task, they are:
https://superfastpython.com/python-asyncio/ 180/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
If the task is scheduled or running, then the caller will suspend until the task is
complete and the return value will be provided.
For example:
1 ...
2 # get the return value from a task
3 value = await task
Unlike a coroutine, we can await a task more than once without raising an error.
For example:
1 ...
2 # get the return value from a task
3 value = await task
4 # get the return value from a task
5 value = await task
We can also get the return value from the task by calling the result() method
(https://docs.python.org/3/library/asyncio-task.html#asyncio.Task.result) on the
asyncio.Task object.
For example:
1 ...
2 # get the return value from a task
3 value = task.result()
This requires that the task is done. If not, an InvalidStateError exception will be
raised.
You can learn more about getting the result from tasks in the tutorial:
https://superfastpython.com/python-asyncio/ 181/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
This can be achieved by calling the asyncio.create_task() function and passing it the
coroutine.
The coroutine will be wrapped in a Task object and will be scheduled for execution.
The task object will be returned and the caller will not suspend.
For example:
1 ...
2 # schedule the task for execution
3 task = asyncio.create_task(other_coroutine())
The task will not begin executing until at least the current coroutine is suspended, for
any reason.
We can help things along by suspending for a moment to allow the task to start
running.
For example:
1 ...
2 # suspend for a moment to allow the task to start running
3 await asyncio.sleep(0)
This will suspend the caller only for a brief moment and allow the ask an opportunity
to run.
This is not required as the caller may suspend at some future time or terminate as
part of normal execution.
We may also await the task directly once the caller has run out of things to do.
https://superfastpython.com/python-asyncio/ 182/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
For example:
1 ...
2 # wait for the task to complete
3 await task
This can be achieved by first getting a set of all currently running tasks via the
asyncio.all_tasks() function.
For example:
1 ...
2 # get a set of all running tasks
3 all_tasks = asyncio.all_tasks()
This will return a set that contains one asyncio.Task object for each task that is
currently running, including the main() coroutine.
We cannot wait on this set directly, as it will block forever as it includes the task that is
the current task.
Therefore we can get the asyncio.Task object for the currently running task and
remove it from the set.
This can be achieved by first calling the asyncio.current_task() method to get the
task for the current coroutine and then remove it from the set via the remove()
method.
For example:
1 ...
2 # get the current tasks
3 current_task = asyncio.current_task()
4 # remove the current task from the list of all tasks
5 all_tasks.remove(current_task)
https://superfastpython.com/python-asyncio/ 183/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
This will suspend the caller until all tasks in the set are complete.
For example:
1 ...
2 # suspend until all tasks are completed
3 await asyncio.wait(all_tasks)
Tying this together, the snippet below added to the end of the main() coroutine will
wait for all background tasks to complete.
1 ...
2 # get a set of all running tasks
3 all_tasks = asyncio.all_tasks()
4 # get the current tasks
5 current_task = asyncio.current_task()
6 # remove the current task from the list of all tasks
7 all_tasks.remove(current_task)
8 # suspend until all tasks are completed
9 await asyncio.wait(all_tasks)
A task that is scheduled and run independently will not stop the event loop from
exiting.
If your main coroutine has no other activities to complete and there are independent
tasks running in the background, you should retrieve the running tasks and wait on
them
https://superfastpython.com/python-asyncio/ 184/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
The done callback function is a regular function, not a coroutine, and takes the
asyncio.Task that it is associated with as an argument.
We can use the same callback function for all tasks and report progress in a general
way, such as by reporting a message.
For example:
For example:
1 ...
2 # add a done callback to a task
3 task.add_done_callback(progress)
The wrapper coroutine may take two arguments, a coroutine and a time in seconds.
It will sleep for the given delay interval in seconds, then await the provided coroutine.
https://superfastpython.com/python-asyncio/ 185/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
To use the wrapper coroutine, a coroutine object can be created and either awaited
directly or executed independently as a task.
For example, the caller may suspend and schedule the delayed coroutine and wait for
it to be done:
1 ...
2 # execute a coroutine after a delay
3 await delay(coro, 10)
Alternatively, the caller may schedule the delayed coroutine to run independently:
1 ...
2 # execute a coroutine after a delay independently
3 _ = asyncio.create_task(delay(coro, 10))
They are:
The task that is completed can issue its own follow-up task.
This may require checking some state in order to determine whether the follow-up
task should be issued or not.
https://superfastpython.com/python-asyncio/ 186/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
For example:
1 ...
2 # schedule a follow-up task
3 task = asyncio.create_task(followup_task())
The task itself may choose to await the follow-up task or let it complete in the
background independently.
For example:
1 ...
2 # wait for the follow-up task to complete
3 await task
The caller that issued the task can choose to issue a follow-up task.
For example, when the caller issues the first task, it may keep the asyncio.Task
object.
It can then check the result of the task or whether the task was completed
successfully or not.
For example:
1 ...
2 # issue and await the first task
3 task = await asyncio.create_task(task())
4 # check the result of the task
5 if task.result():
6 # issue the follow-up task
7 followup = await asyncio.create_task(followup_task())
https://superfastpython.com/python-asyncio/ 187/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
For example, the caller that issues the task can register a done callback function on
the task itself.
The done callback function must take the asyncio.Task object as an argument and
will be called only after the task is done. It can then choose to issue a follow-up task.
The done callback function is a regular Python function, not a coroutine, so it cannot
await the follow-up task
1 # callback function
2 def callback(task):
3 # schedule and await the follow-up task
4 _ = asyncio.create_task(followup())
The caller can issue the first task and register the done callback function.
For example:
1 ...
2 # schedule and the task
3 task = asyncio.create_task(work())
4 # add the done callback function
5 task.add_done_callback(callback)
For example:
1 ...
2 # execute a function in a separate thread
3 await asyncio.to_thread(task)
The task will not begin executing until the returned coroutine is given an opportunity
to run in the event loop.
This is in the low-level asyncio API and first requires access to the event loop, such as
via the asyncio.get_running_loop() function
(https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.get_running_loop).
If None is provided for the executor, then the default executor is used, which is a
ThreadPoolExecutor.
For example:
https://superfastpython.com/python-asyncio/ 189/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
1 ...
2 # get the event loop
3 loop = asyncio.get_running_loop()
4 # execute a function in a separate thread
5 await loop.run_in_executor(None, task)
The caller must manage the executor in this case, shutting it down once the caller is
finished with it.
For example:
1 ...
2 # create a process pool
3 with ProcessPoolExecutor as exe:
4 # get the event loop
5 loop = asyncio.get_running_loop()
6 # execute a function in a separate thread
7 await loop.run_in_executor(exe, task)
8 # process pool is shutdown automatically...
That being said, there may also be some misunderstandings that are preventing you
from making full and best use of the capabilities of the asyncio in Python.
In this section, we review some of the common objections seen by developers when
considering using the asyncio.
https://superfastpython.com/python-asyncio/ 190/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
As such the GIL is not an issue when using asyncio and coroutine.
Coroutines run and are managed (switched) within the asyncio event loop in the
Python runtime.
In this sense, Python does not have support for “native coroutines”, but I’m not sure
such things exist in modern operating systems.
It has for a long time now and it is widely used in open source and commercial
projects.
https://superfastpython.com/python-asyncio/ 191/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
Python is commonly used for glue code, one-off scripts, but more and more for large-
scale software systems.
If you are using Python and then you need concurrency, then you work with what you
have. The question is moot.
If you need concurrency and you have not chosen a language, perhaps another
language would be more appropriate, or perhaps not. Consider the full scope of
functional and non-functional requirements (or user needs, wants, and desires) for
your project and the capabilities of different development platforms.
Any program developed using threads can be rewritten to use asyncio and
coroutines.
Any program developed using coroutines and asyncio can be rewritten to use
threads.
Many use cases will execute faster using threads and may be more familiar to a wider
array of Python developers.
Some use cases in the areas of network programming and executing system
commands may be simpler (less code) when using asyncio, and significantly more
scalable than using threads.
https://superfastpython.com/python-asyncio/ 192/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
Further Reading
This section provides additional resources that you may find helpful.
Guides
APIs
https://superfastpython.com/python-asyncio/ 193/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
References
Conclusions
This is a large guide, and you have discovered in great detail how asyncio and
coroutines work in Python and how to best use them in your project.
https://superfastpython.com/python-asyncio/ 194/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
(https://marvelous-writer-
6152.ck.page/99ee689b9b)
Your free book "Parallel Loops in Python" includes complete and working code
templates that you can modify and use right now in your own projects.
https://superfastpython.com/python-asyncio/ 195/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
Comments
Alexey says
DECEMBER 4, 2022 AT 10:41 AM (HTTPS://SUPERFASTPYTHON.COM/PYTHON-ASYNCIO/#COMMENT-364)
Thank you! It was wonderful. Now I would like to see a complete guide about low-level asyncio api. It would be
great, thank you))
REPLY
Great idea!
REPLY
REPLY
REPLY
REPLY
https://superfastpython.com/python-asyncio/ 196/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
Thank you!
REPLY
Thank you for this guide. I learnt a lot. In particular, I learned the reason why my MicroPython program fails to
work.
It tries to use uasyncio (a cut down version of asyncio for MicroPython run on microprocesors). My program
also uses lv_micropython which is the LVGL graphical user interface framework running within MicroPython.
My program implements a voice reminder application in MicroPython on an M5Stack Core2.
I’ve succeeded in using uasyncio Stream (StreamReader wrapping the microphone device) to capture audio
from the microphone into a buffer and then write it to a file. At the moment this only works as a stand alone
program. Also, as another stand alone program, I can play the recorded audio file through the speaker using
uasyncio.StreamWriter to wrap the speaker device.
My problem comes when trying to drive this code from the LVGL user interface. Specifically, I need to record
audio as long as a record button remains pressed and listen to the recorded audio as long as a listen button
remains pressed (or the whole audio file if the listen button is pressed for as long as the audio reminder lasts).
To implement the listen button I tried to call uasyncio.create_task(speaker.play_wav(“/recorded.wav”)) when
the listen button is pressed and stop the playing when the listen button is released. The stopping is done by
setting a stopped flag tested by a ‘while not stopped’ loop that plays successive chunks of the audio file to the
speaker.
The problem seems to be that the button event callback function, registered with the listen button and called
by LVGL in response to the button being pressed or released, is not a coroutine. It therefore can’t successfully
call uasyncio.create_task(). The same problem will arise for recording while a record button is pressed, when I
come to implement that.
I would appreciate any help and advice you can give on how I might work around this problem or approach
things in a different way that avoids creating the playing (or recording) task from the non-coroutine callback.
I realise this is a long comment but I tried to keep it succinct.
Thanks in advance for your help.
Paul
REPLY
Perhaps you can check of the audio API you are using supports asyncio or not. If not, you will need to
issue the blocking calls to a thread pool via asyncio.run_in_executor() or asyncio.to_thread().
Tomas says
FEBRUARY 11, 2023 AT 1:10 AM (HTTPS://SUPERFASTPYTHON.COM/PYTHON-ASYNCIO/#COMMENT-1345)
Thanks you for guide. He allowed to streamline knowledge about asyncio. Is it possible to control the number
of coroutines running at the same time? For example, the simultaneous work of only 10 out of 1000
coroutines and the addition of new tasks to replace the completed ones. I was able to implement this using
the threading module, but the possibility of implementing such logic using asyncio is interesting.
REPLY
Tomas says
FEBRUARY 11, 2023 AT 2:07 AM (HTTPS://SUPERFASTPYTHON.COM/PYTHON-ASYNCIO/#COMMENT-1346)
REPLY
REPLY
Thank you for writing such a clear guide. I will absolutely be book-marking this and coming back to it regularly.
There is a clarification that I would like to ask you for. We see it is possible to execute a coroutine (or rather,
schedule it for execution) using await but it is also possible to execute a coroutine using event loop methods
such as run_until_complete()
What is the difference between them? Is one preferable over the other? My guess is that await is more friendly
https://superfastpython.com/python-asyncio/ 198/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
since it could allow other tasks to run as well as the desired one, whereas run_until_complete() is immediately
blocking i.e all other running tasks are suspended until the desired one completes. Anyway that’s my guess
but I’d really love to get your insight about this. Thanks again.
REPLY
Yes, generally we would schedule coroutines using await and not interact with the event loop directly.
The API on the event loop is intended for library developers, not application developers. You can learn
more about this here:
https://superfastpython.com/asyncio-apis/ (https://superfastpython.com/asyncio-apis/)
REPLY
Chris says
MAY 5, 2023 AT 12:41 AM (HTTPS://SUPERFASTPYTHON.COM/PYTHON-ASYNCIO/#COMMENT-3688)
Hi Jason,
This is the best guide for asyncio I have read. There are many conceptual foundations you have laid for the
reader to really understand the big picture. Without those, all the functions are really just a mystery, and
prone to be misused.
If possible, it would be great if you could point us to some references that explain more about how the event
loop interacts with different types of tasks, some of which are IO tasks, and some are Python coroutines.
Is it the case that the async IO tasks run in a separate process, and the operating system will inform the event
loop of its completion through some kind of callback?
What about asyncio.sleep()? It seems it does not actually take up CPU time: if t1 involves asyncio.sleep(1) and
t2 involves actual work that takes 1 sec, and the caller coroutine uses await asyncio.gather(t1, t2), both t1 and
t2 will finish in 1 sec.
How is event loop implemented? If there is only a single task asyncio.sleep(3) on the loop, does it periodically
check whether the task finishes using poll, or it waits for an interrupt?
REPLY
https://superfastpython.com/python-asyncio/ 199/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
I don’t have a ton on the event loop, this might be a good start:
https://superfastpython.com/asyncio-event-loop/ (https://superfastpython.com/asyncio-event-loop/)
Implementation details differ, e.g. there are different types of event loops.
After that, you might want to dig into the docs here:
https://docs.python.org/3/library/asyncio-eventloop.html (https://docs.python.org/3/library/asyncio-
eventloop.html)
REPLY
Chris says
MAY 7, 2023 AT 6:12 AM (HTTPS://SUPERFASTPYTHON.COM/PYTHON-ASYNCIO/#COMMENT-3766)
REPLY
Mathieu says
MAY 17, 2023 AT 7:26 PM (HTTPS://SUPERFASTPYTHON.COM/PYTHON-ASYNCIO/#COMMENT-4173)
Hello !
This website looks very interesting and well made ! But before spending hours reading and learning, I want to
know if it would help me in my situation, let me explain.
I have to develop a GUI in Python but I have to use asyncio in order to make calculations fatser and overall : I
need to make a restart button for the user if he wants to stop the process.
Would I learn how to do theses things by reading our work ?
REPLY
Sorry, I don’t have any tutorials on developing GUIs and adding asyncio. I may cover the topic in the
future.
REPLY
Tim says
MAY 18, 2023 AT 1:37 PM (HTTPS://SUPERFASTPYTHON.COM/PYTHON-ASYNCIO/#COMMENT-4190)
https://superfastpython.com/python-asyncio/ 200/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
This is the best guide for asyncio that I’ve stumbled across. There are other guides that suffice, but your
explanation of each component in asyncio is quite helpful.
REPLY
REPLY
REPLY
REPLY
These two concepts appear to be in conflict with each other. Can explain this further and resolve this conflict?
REPLY
https://superfastpython.com/python-asyncio/ 201/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
1) Speed to start.
2) Speed of program overall.
Starting a thread is slower than starting a coroutine. The rationale is that a thread is a heavier object
(e.g. native thread in the OS) whereas a coroutine is just a type of routine (e.g. a function). The
benchmark results bare this out:
https://superfastpython.com/coroutines-faster-threads/ (https://superfastpython.com/coroutines-
faster-threads/)
Now consider a program that does a ton of socket IO. It’s slow. We can make it faster by doing the IO
concurrently with threads. WIN! We can also make it faster by doing the IO concurrently with
coroutines. WIN! They are different, e.g. blocking IO vs non-blocking IO and threads vs coroutines, but
they both offer a benefit. The comment in this guide that “Asyncio is faster than threads is false” holds
and does not conflict.
Although we can make a slow sequential program faster with threads or with asyncio, the asyncio
version will not be faster than the threaded version. This has to be stated because many developers
wrongly believe that using asyncio will be faster than threads.
This is a general statement, e.g. it is expected to hold across programs, across tasks.
Nevertheless, if we focus our microbenchmark on startup time, we will see that coroutines will indeed
start faster than the threads. But if we expand our benchmark to the entire program, this benefit is
expected to be washed out by the variability of running the program while other programs are
running on the same OS/CPU, of the variability of socket IO, etc. Put another way, they will perform
about the same when the whole program is considered.
You can see examples of this when doing file IO here, the asyncio versions are about as fast as the
threaded versions (admitted the async fileio is simulated using a thread pool under the covers):
https://superfastpython.com/learning-paths/#File_IO_Concurrency_Learning_Path
(https://superfastpython.com/learning-paths/#File_IO_Concurrency_Learning_Path)
I’ll develop a socket io example to make this point clearer. Thank you kindly for the prompt and
question!
REPLY
https://superfastpython.com/python-asyncio/ 202/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
SEPTEMBER 21, 2023 AT 1:50 AM (HTTPS://SUPERFASTPYTHON.COM/PYTHON-ASYNCIO/#COMMENT-9352)
Consider an async python web server (eg. Uvicorn, implementing ASGI) connected to a
synchronous application (Django). In this setup, Uvicorn creates a coroutine for each request,
sends it to Django, which must create a new thread per request because it needs to flip its
context from sync to async. This thread can remain synchronous and you achieve a level of
concurrency because as the thread makes blocking I/O calls, it relinquishes it’s GIL and
another thread takes over.
An alternative setup is to convert Django to fully asynchronous and thus there would be only
one thread in the system (the Uvicorn event-loop thread). It would handle each request via a
coroutine. In Django, each request’s call flow would need to become async to avoid blocking
the event-loop when it makes an I/O call (to a database or another server).
Based on your logic, the second setup starts the coroutines faster but the first setup might
complete the full request-to-response cycle faster. This is surprising because we’ve been led to
believe that single-threaded event-loop-based web servers are generally faster than multi-
threaded servers.
What’s your take on these two scenarios and can you incorporate server load into the answer
(i.e. do threads win at higher loads or vice versa)?
REPLY
Great example!
From a performance persective, requests can be served as fast with threads as with
corutines. No differences. Neither is faster.
There is a fixed startup time. Corutines are faster to start than threads, threads are
faster to start than processes. We overcome this fixed startup time by starting each
worker once and maintaining a pool of live request handlers (of some size…).
https://superfastpython.com/python-asyncio/ 203/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
it’s written off and most concurrency people say that coroutines are no faster than
threads. This holds for fixed-duration and variable-duration tasks.
The rationale here that threads faster than coroutines, I guess, is that there is a cost to
suspend/resume in one thread that adds up to more than context switching between
OS-level threads, perhaps. Or failing to go “async all the way down” adds up many
micro blocking calls that impact overall performance. Not seen this myself yet. But
maybe I’ve not scaled enough.
Another key aspect we have not touched on in scalability. We can run more coroutines
in a system than threads simply because we need less memory to maintain a
coroutine than an os thread. See this tutorial:
UPDATE: I have run some tests comparing coroutines to threads, you can see the
results here:
Mudit says
NOVEMBER 24, 2023 AT 4:26 AM (HTTPS://SUPERFASTPYTHON.COM/PYTHON-ASYNCIO/#COMMENT-10061)
https://superfastpython.com/python-asyncio/ 204/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
In fact, the task will not execute until the event loop has an opportunity to run.
This will not happen until all other coroutines are not running and it is the task’s turn to
run.
I think you meant: This will not happen until all other coroutines ARE running ?
REPLY
No, it is is correct.
The event loop will only execute one coroutine at a time, even though multiple coroutines are
“running”.
REPLY
A new book designed to teach you the asyncio module step-by-step, super
fast!
https://superfastpython.com/python-asyncio/ 205/206
2/21/24, 6:59 PM Python Asyncio: The Complete Guide - Super Fast Python
https://superfastpython.com/python-asyncio/ 206/206