Last Updated on November 14, 2023
You can understand asyncio better by developing a “hello world” program.
The asyncio module provides coroutine-based concurrency in Python.
The first program we typically develop when starting with a new programming language or programming paradigm is called “hello world“.
Asyncio programs are different, they use the asynchronous programming paradigm. Therefore, to understand this paradigm, we need to develop an asyncio hello world program.
In this tutorial, you will discover how to develop a “hello world” program in asyncio.
After completing this tutorial, you will know:
- How to develop the simplest asyncio program.
- How to read and understand how the asyncio “hello world” program works in detail and how it might go wrong.
- How to develop a slightly more complex “hello world” program that helps us understand coroutines.
Let’s get started.
Hello World
The first program we write in a new programming language is a “hello world” program.
The asyncio module in Python is different from most other modules.
Using it is like writing code in a new programming language. It’s different from “normal” Python.
It is the reason why so many Python developers are excited to use it, and why others are afraid to get started.
The first step into asyncio is to write a hello world. The second step is to understand what it does.
Run loops using all CPUs, download your FREE book to learn how.
Asyncio Hello World
Let’s write a hello world for asyncio.
The complete example is listed below.
Type the example and run it.
Or copy-paste it.
This is your first step on the asyncio journey.
1 2 3 4 5 6 7 8 9 10 11 |
# SuperFastPython.com # hello world program for asyncio import asyncio # define a coroutine async def custom_coroutine(): # report a message print('Hello world') # execute the coroutine asyncio.run(custom_coroutine()) |
Running the example reports the “hello world” message.
1 |
Hello world |
Did it run okay for you?
Ensure you are using Python 3.7 or higher.
Full of questions? Good!
Now, let’s slow everything down and understand what this example does.
Asyncio Hello World in Detail
We know how to type and run a hello world for asyncio, now let’s understand what it does (at least from a birds-eye view).
What should a hello world program for asyncio do?
If asyncio is for coroutine-based concurrency, we should create and run a coroutine.
If we were exploring thread-based concurrency in the threading module, we would create and run a thread. This would be the same if we were exploring process-based concurrency in the multiprocessing module.
Recall, a routine is a function. For example, we can define a function to print hello world as follows:
1 2 3 4 |
# custom routine def custom_routine(): # report a message print('Hello world') |
A coroutine is a function that can be paused.
We can define a coroutine just like a normal function, except it has the added “async” keyword before the “def” keyword.
For example:
1 2 3 4 |
# define a coroutine async def custom_coroutine(): # report a message print('Hello world') |
So far, so good.
We cannot execute a coroutine like a routine.
If we call our custom_coroutine() directly, we get warning messages that look like an error.
For example:
1 2 3 4 5 6 7 8 9 10 11 |
# SuperFastPython.com # example of calling a coroutine directly import asyncio # define a coroutine async def custom_coroutine(): # report a message print('Hello world') # call the coroutine directly custom_coroutine() |
Running the example results in warning messages that look scary.
The first says that we never “awaited” our coroutine.
The second is not at all helpful.
1 2 3 4 |
... hello_world2.py:10: RuntimeWarning: coroutine 'custom_coroutine' was never awaited custom_coroutine() RuntimeWarning: Enable tracemalloc to get the object allocation traceback |
We cannot call a coroutine directly.
Instead, we must have the routine called for us by the asyncio run time, called the event loop.
This can be achieved by calling the asyncio.run() module function.
This function will start the asyncio event loop in the current thread, and we can only have one of these running in a thread at a time.
It takes a coroutine as an argument.
Not the name of the coroutine, like the “target” argument of a threading.Thread or multiprocessing.Process, instead it takes an instance of a coroutine.
We can create an instance of a coroutine just like creating a Python object, and it looks like we are calling the coroutine.
For example:
1 2 3 |
... # execute the coroutine asyncio.run(custom_coroutine()) |
This will start the event loop and execute the coroutine and print the message.
But, let’s unpack this further.
If we are creating an instance of a coroutine and passing it to the run() module function to execute, why not assign it first?
For example:
1 2 3 4 5 |
... # create the coroutine and assign it to a variable coro = custom_coroutine() # execute the coroutine asyncio.run(coro) |
This makes more sense now.
We can clearly see the creation of the coroutine and it is passed to the run() function for execution.
There’s one more thing.
If we don’t execute an instance of a coroutine, we will get a warning.
This means that if we create and assign an instance of our custom coroutine and do not pass it to asyncio.run(), a warning message is reported.
For example:
1 2 3 4 5 6 7 8 9 10 11 |
# SuperFastPython.com # example of calling a coroutine directly import asyncio # define a coroutine async def custom_coroutine(): # report a message print('Hello world') # create the coroutine and assign it to a variable coro = custom_coroutine() |
Running the example creates the coroutine, but does not do anything with it.
The Python runtime then reports this as a warning message, similar to what we saw when we called the coroutine directly.
1 |
sys:1: RuntimeWarning: coroutine 'custom_coroutine' was never awaited |
This is in the spec.
When a native coroutine is garbage collected, a RuntimeWarning is raised if it was never awaited on
— PEP 492 – Coroutines with async and await syntax
I suspect it’s aimed to be helpful to new programmers using the new and confusing asyncio, highlighting they created a thing that was never used.
Strange though.
It would be like creating a threading.Thread and never calling the start() method. It does nothing, and there’s little reason to do it, but we don’t need to raise a runtime warning, do we?
So now we know what our hello world example does.
Let’s do one more thing to make it cooler.
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.
Slightly Better Hello World
The thing about coroutines is that they can be paused or “suspended“.
Technically threads can be paused. The operating system can suspend a thread at any time and resume it again later.
But coroutines are different. We can specify the point at which they will be paused and the pausing and unpausing are controlled within the asyncio runtime, e.g. the event loop.
A coroutine can pause and wait for some condition via the “await” keyword.
Specifically, a coroutine can await an awaitable.
An awaitable can be another coroutine or some IO, like reading data from a socket connection.
The function we use to simulate blocking in concurrent programming is sleep().
For example:
So let’s update our hello world program so that our coroutine performs a sleep.
The asyncio module provides a sleep() module function.
For example:
1 2 3 4 5 6 |
# define a coroutine async def custom_coroutine(): # block asyncio.sleep(1) # report a message print('Hello world') |
But we cannot just call the asyncio.sleep() function.
Doing so results in unexpected behavior and another warning message.
For example:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# SuperFastPython.com # hello world program for asyncio that tries to sleep import asyncio # define a coroutine async def custom_coroutine(): # block asyncio.sleep(1) # report a message print('Hello world') # execute the coroutine asyncio.run(custom_coroutine()) |
Running the example reports our message as before, but the example does not sleep.
Instead, we get a warning message just like the warning message we got when we created a coroutine and did not use it. This is key.
1 2 3 4 |
hello_world4.py:8: RuntimeWarning: coroutine 'sleep' was never awaited asyncio.sleep(1) RuntimeWarning: Enable tracemalloc to get the object allocation traceback Hello world |
In fact, the asyncio.sleep() is creating and returning a coroutine.
The coroutine is not being used, it gets garbage collected and the Python interpreter tells us we did not use it for some reason.
We can make this clear if we assign the return value from asyncio.sleep(), which no one does by the way.
1 2 3 |
... # create and store a sleep coroutine sleeper = asyncio.sleep(1) |
We do not need to pass it to asyncio.run(), because our custom_coroutine() coroutine is already being executed within the asyncio event loop.
Instead, we can request that the current coroutine pause, execute the new coroutine, and wait for the new coroutine to finish.
This can be achieved using the await keyword within a coroutine.
For example:
1 2 3 4 5 |
... # create and store a sleep coroutine sleeper = asyncio.sleep(1) # pause the current coroutine and execute the new coroutine await sleeper |
Or, we do this on one line and await the return value from asyncio.sleep() directly.
1 2 3 |
... # block await asyncio.sleep(1) |
Great!
The complete example is listed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# SuperFastPython.com # hello world program for asyncio that sleeps import asyncio # define a coroutine async def custom_coroutine(): # block await asyncio.sleep(1) # report a message print('Hello world') # execute the coroutine asyncio.run(custom_coroutine()) |
Running the example prints the message, as before, but does more.
The run() method starts the asyncio runtime.
We create an instance of our custom coroutine and pass it to the asyncio runtime to execute.
It’s executed, and the first thing it does is it creates another coroutine and asks it to be executed by the event loop, and waits for it to finish.
The sleep coroutine is executed and blocks for one second.
The sleep coroutine finishes and our custom coroutine is unpaused.
The message is reported, then the asyncio event loop is shut down.
1 |
Hello world |
One final point, we cannot await our custom coroutine in order to execute it.
For example:
1 2 3 |
… # execute the coroutine await custom_coroutine() |
The await keyword can only be used within a coroutine to execute and wait on an awaitable.
Put another way, the await keyword must be used within the asyncio event loop. Only asyncio.run() can be used as the entry point into an asyncio program.
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 how to develop a hello world program using asyncio.
Do you have any questions?
Ask your questions in the comments below and I will do my best to answer.
Do you have any questions?