A coroutine is an awaitable that can be wrapped in a Task. A coroutine can pause execution and wait for an awaitable object to be done via the await keyword.
In this tutorial, you will discover asyncio awaitables and how to create and use them in Python.
Let’s get started.
What is an Awaitable?
An awaitable is a Python object that can be waited on using the “await” keyword.
We say that an object is an awaitable object if it can be used in an await expression.
— Coroutines and Tasks
For example:
1 2 3 |
... # await an awaitable await awaitable |
An awaitable allows a coroutine in asyncio to pause execution and wait for the specified awaitable to be done.
A done coroutine means that the coroutine finished normally, returned explicitly, was canceled, or failed with an error or exception.
There are three main types of awaitables that may work within our asyncio programs, they are:
- Coroutine
- Future
- Task
Coroutines are awaitable, which means that a coroutine may wait on another coroutine.
A task is a coroutine that is wrapped and executed independently. The task provides a handle on the independently exciting coroutine, allowing us to check its status or get the result when needed. We may also wait for it to complete.
Tasks are used to schedule coroutines concurrently.
— Coroutines and Tasks
The Task class extends the Future class, which is also awaitable.
Generally, we do not create Future objects as part of the normal use of the asyncio module.
A Future is a special low-level awaitable object that represents an eventual result of an asynchronous operation. […] Normally there is no need to create Future objects at the application level code.
— Coroutines and Tasks
Now that we know what an awaitable is, let’s look at how we might create them.
Run loops using all CPUs, download your FREE book to learn how.
How to Create an Awaitable?
There are two main ways to create an awaitable, they are:
- Create a Coroutine
- Create a Task
There are other ways, such as creating custom Future objects, but we will not consider them in this tutorial.
Let’s take a closer look at each of these examples.
Create a Coroutine
A coroutine is defined like a function (like a routine).
The important difference is that it has the “async” keyword before the “def” keyword in the function definition.
For example:
1 2 3 |
# define a coroutine async def custom_funcion(): # ... |
We can create an awaitable from a coroutine definition by creating an instance of the coroutine.
This looks like calling the coroutine, but in fact, returns a coroutine object.
For example:
1 2 3 |
... # create the coroutine coro = custom_coroutine() |
This creates an instance of the coroutine which is an awaitable.
A coroutine may then await it directly.
For example:
1 2 3 |
... # await the custom coroutine await coro |
This is typically performed in a single line.
For example:
1 2 3 |
... # await the custom coroutine await custom_coroutine() |
Create a Task
A Task wraps a coroutine and will schedule the coroutine for execution, when possible, and provides a handle on the coroutine.
As such, a task is created using a coroutine. This can be achieved using the asyncio.create_task() function.
For example:
1 2 3 4 5 |
... # create the coroutine coro = custom_coroutine() # create and schedule a coroutine as a task task = asyncio.create_task(coro) |
The task will execute whenever possible.
Nevertheless, a coroutine may pause execution and wait for the task to complete because it is an awaitable.
For example:
1 2 3 |
... # await the custom coroutine wrapped in a task await task |
This may all be achieved in one compound statement, which can be most confusing to beginners.
For example:
1 2 3 |
... # await the custom coroutine wrapped in a task await asyncio.create_task(custom_coroutine()) |
Now that we know how to create awaitables, let’s look at some worked examples.
Example of Coroutine Awaitable
We can explore a coroutine as an awaitable.
In this example, we will run a coroutine that will create another coroutine which is then awaited. This both schedules the new coroutine for execution and pauses the execution of the current coroutine until the new coroutine is done.
The complete example is listed.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# SuperFastPython.com # example of a coroutine as an awaitable import asyncio # definition of another coroutine async def another_coroutine(): print('Another coroutine') # define a custom coroutine async def custom_coroutine(): print('hello world') # await another coroutine await another_coroutine() # start the asyncio program asyncio.run(custom_coroutine()) |
Running the example first creates the custom coroutine, then uses it to start the asyncio event loop.
The coroutine executes and reports a message. I then create a second coroutine and schedule it for execution. This pauses the execution of the first coroutine until the second coroutine is done.
The second coroutine runs and reports a message, then is done.
The first coroutine then continues on and is also done.
This highlights how we may treat a coroutine as an awaitable and await it from within another coroutine.
1 2 |
hello world Another coroutine |
Free Python Asyncio Course
Download your FREE Asyncio PDF cheat sheet and get BONUS access to my free 7-day crash course on the Asyncio API.
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.
Example of Task Awaitable
We can explore a Task as an awaitable.
In this example, we will run a coroutine that will create a second coroutine that will be wrapped in a task. The first coroutine will then await the task, treating it directly as an awaitable.
The complete example is listed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# SuperFastPython.com # example of a task as an awaitable import asyncio # definition of a another coroutine async def another_coroutine(): print('Another coroutine') # define a custom coroutine async def custom_coroutine(): print('hello world') # await a task await asyncio.create_task(another_coroutine()) # start the asyncio program asyncio.run(custom_coroutine()) |
Running the example creates a coroutine and starts the asyncio program.
The coroutine executes and reports a message. It then creates a second coroutine, wraps it in a task, and then awaits it.
This schedules the independent second coroutine for execution in the asyncio event loop and pauses the execution of the first coroutine until the awaitable is complete.
The task executes, in turn executing the second coroutine that reports a message.
The task completes and returns control to the first coroutine when it finishes.
This highlights how we might create a task and treat it as an awaitable within a coroutine.
1 2 |
hello world Another coroutine |
Overwhelmed by the python concurrency APIs?
Find relief, download my FREE Python Concurrency Mind Maps
Further Reading
This section provides additional resources that you may find helpful.
Python Asyncio Books
- Python Asyncio Mastery, Jason Brownlee (my book!)
- Python Asyncio Jump-Start, Jason Brownlee.
- Python Asyncio Interview Questions, Jason Brownlee.
- Asyncio Module API Cheat Sheet
I also recommend the following books:
- Python Concurrency with asyncio, Matthew Fowler, 2022.
- Using Asyncio in Python, Caleb Hattingh, 2020.
- asyncio Recipes, Mohamed Mustapha Tahrioui, 2019.
Guides
APIs
- asyncio — Asynchronous I/O
- Asyncio Coroutines and Tasks
- Asyncio Streams
- Asyncio Subprocesses
- Asyncio Queues
- Asyncio Synchronization Primitives
References
Takeaways
You now know what awaitables are and how to create and use them in Python.
Do you have any questions?
Ask your questions in the comments below and I will do my best to answer.
Photo by Jakob Rosen on Unsplash
Do you have any questions?