Easy Introduction to Asyncio - Asynchronous Programming in Python
What is Asynchronous
? Asynchronous implies that something is happening not at the same time or simultaneously.
A definition of the same from The Art of Concurrency book states:
“Separate execution streams that can run concurrently in any order relative to each other are asynchronous.”
For example, “Asynchronous Function Call” implies that the function has been called and while it executes, you can carry on with other tasks. Later on you can see whether the function has returned something or not.
What is Future
? Future allows you to get the results from an asynchronous function call. Meaning, while we made an asynchronous function call, we went on to execute the remaining tasks, and later on we want to check or get the results of that particular function. In essence, Future is a place that you reserve for a value that will be generated in the future!
The above diagram shows what an asynchronous task
comprises of.
Example scenario:
- Consider you have
task1
,task2
andtask3
. -
task2
is a complicated and long-running task. -
task1
andtask3
are not dependent ontask2
. - So instead for waiting for
task2
to be completed, and then moving on to thetask3
, we can lettask2
be executed asynchronously and meanwhile complete all other tasks. - When
task2
is done, we will be notified of it and we can retrieve the results.
The real world application for this is primarily in non-blocking I/O operations. Where reading and writing operations are done asynchronously. This is where the term Asyncio
comes from probably, where non-blocking IO operations are implemented using asynchronous programming.
“This lets us handle multiple I/O operations at once, while still allowing our application to remain responsive.” - Python Concurrency with asyncio
Thus, Asyncio enables us to do asynchronous programming in python using coroutines. What is a coroutine
? It is function that can be called, suspended at any point and resumed at any time. Let’s define a coroutine function
import asyncio
async def my_coroutine():
#-
#-
#-
What async
in the above example does is, it wraps the function my_coroutine()
. and when you call the function, it actually returns an object which is asynchronous, and can be used.
Let’s try to call a async function like we would a regular function:
#importing the library
import asyncio
#defining a coroutine
async def print_something():
print("Hey Ash!")
#calling the function
print_something()
Trying to run the above block of code will produce the following error.
RuntimeWarning: coroutine 'print_something' was never awaited
In order to execute the coroutine we use await
keyword. Remember, the keyword await
must always be written inside a async function and never outside it, or else you will get an error.
Let’s try again, this time using asyncio.run()
#importing the library
import asyncio
#defining a coroutine
async def print_something():
print("Hey Ash!")
#calling the function
asyncio.run(print_something())
Here, asyncio
created an event loop, that enables us to execute our coroutine.
Hey Ash!
Now we will write a better code to understand await
keyword.
#import asyncio library
import asyncio
#create an async function
async def task1():
print("Hey Ash")
await task2(10) #wait will the task2 is fully executed
print("Hello World")
#create an asunc function
async def task2(num):
for i in range(num):
print(i)
await asyncio.sleep(1) #wait for 1 second before printing the next i
#run the coroutine task1
asyncio.run(task1())
In the above example the following steps are happening:
- First we import the library of
asyncio
. - We write a async function
task1
to print “Hey Ash”, then called another async functiontask2
and asked thetask1
to wait untiltask2
has finished its execution. This is done by using theawait
keyword. After thistask1
has one more line left to execute, which is “Hello World”. - The
task2
is to print numbers in a range. After printing each number, we used anawait
keyword to make the process sleep for1
second and then move on to print another number. - At last, we run the
task1
usingasyncio.run(task1)
Hey Ash
0
1
2
3
4
5
6
7
8
9
Hello World
- As you can see when we call the
task1
, it prints “Hey Ash” and then the next line is to wait for the complete execution oftask2
. - Thus, the numbers 1 to 9 are printed and then it returns back to the last line in
task1
which is to print “Hello World”.
Now what will be the output for the following block of code? Run it yourself.
#import asyncio library
import asyncio
#create an async function
async def task1():
await task2(5) #wait will the task2 is fully executed
print("Hello World")
#create an asunc function
async def task2(num):
for i in range(num):
print(i)
await asyncio.sleep(1) #wait for 1 second before printing the next i
#run the coroutine task1
asyncio.run(task1())
The result of the above code is as follows:
0
1
2
3
4
Hello World
The task1
could only execute the print statement of “Hello World” after waiting for the task2
to be executed completely.
Now, to disappoint you a little bit, that is, if you haven’t noticed it yet, all the above code weren’t exactly asynchronous. Because, you made a task wait while the other task was working. This is not not we are aiming for right? This happened because we haven’t yet created an asynchronous task.
So let’s create one now.
#import asyncio library
import asyncio
#create an async function
async def task1():
task = asyncio.create_task(task2(5)) #Create our 1st asynchronous task!
print("Hello World")
#create an asunc function
async def task2(num):
for i in range(num):
print(i)
await asyncio.sleep(1) #wait for 1 second before printing the next i
#run the coroutine task1
asyncio.run(task1())
In the above code, within the task1, we created a asynchronous task
using asyncio.create_task()
. I just realized that you might be confused between task1, task2, and task. That’s my bad. task1
and task2
are async functions and NOT asynchronous tasks. But task
is an asynchronous task. Hmm, so much for writing a blog.
Now, what we did by creating a task is that, we are asking python, to let the function task1
execute it’s “Hello World” command while task2
function is busy executing printing numbers. Thus no one’s waiting around for anybody in here.
The result of the above code is as follows:
Hello World
0
You might wonder, what happened to the numbers 1, 2, 3, and 4? Before they could be printed, the task1
terminated. To explain in simple way, consider A and B. A is supposed to to do some task and is also meant to call B to do some task as well. A’s task is a small one, but B has a lot to do. While B was completing his chores, A completed his and did not bother to wait for B to finish his. This ended the main process, since it was A who called B in the first place.
To avoid this, we add a line towards the end of the task1
function, so that task1
waits after it has completed executing all its code, so that task2
can execute everything as well. We add await
as follows.
#import asyncio library
import asyncio
#create an async function
async def task1():
task = asyncio.create_task(task2(5)) #Create our 1st asynchronous task!
print("Hello World")
await task
#create an asunc function
async def task2(num):
for i in range(num):
print(i)
await asyncio.sleep(1) #wait for 1 second before printing the next i
#run the coroutine task1
asyncio.run(task1())
The result of the above code will be as follows:
Hello World
0
1
2
3
4
Now For our last example, run the following code and try to understand what is happening.
#importing the library
import asyncio
async def print_something():
print("Hey Ash!")
task = asyncio.create_task(print_numbers(10))
await asyncio.sleep(5)
print("Finish")
await task
async def print_numbers(num):
for i in range(num):
print(i)
await asyncio.sleep(1)
asyncio.run(print_something())
The result is as follows:
Hey Ash!
0
1
2
3
4
Finish
5
6
7
8
9
In the above example, the print_something
function had 3 jobs, to print “Hey Ash!”, then call the function print_numbers
, and at last to print “Finish”. While the print numbers was executing, it printed out “Hey Ash!” and then after 5 seconds printed out “Finish”. It did not wait for print_numbers to complete the executing. This is a very basic example to show the power of asynchronous programming.
I hope the blog was able to shed some insight into asynchronous programming for you to start with. Follow up on the next blog to dive in deeper.