Asyncio Hello World Tutorial in Python
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.
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.
# 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.
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:
# 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:
# 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:
# 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.
...
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:
...
# 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:
...
# 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:
# 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.
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.
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:
# 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:
# 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.
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.
...
# 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:
...
# 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.
...
# block
await asyncio.sleep(1)
Great!
The complete example is listed below.
# 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.
Hello world
One final point, we cannot await our custom coroutine in order to execute it.
For example:
…
# 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.
Takeaways
You now know how to develop a hello world program using asyncio.
If you enjoyed this tutorial, you will love my book: Python Asyncio Jump-Start. It covers everything you need to master the topic with hands-on examples and clear explanations.