Python is an interpreted language. Which means by default it executes code line by line freely at runtime. But sometimes when you execute your code there are certain places where your whole code blocks for a request to finish or it gets stuck due to complex computations. And then all of a sudden your whole code blocks which makes your whole program block and you wish there was a way to run the damn thing seperately on its own and somehow later notified you when it finished. And in the mean time you could execute other code and comeback to it later when you got notified of the finished task.
This is exactly what asyncio does for you. You can run your code concurently along side other code in harmony without blocking each other.
We’ll be using Python 3.10 for this article where the asyncio library has been made a lot easier to use. I cannot guarantee the workability of your code if you use other versions.
Let’s jump in.
First things first. Let’s get familiarized about the terms used for the asyncio library. I will explain everything at a beginner level and will try to keep things as simple to grasp as possible. If you want to dive deeper into this topic, check python’s official documentions where everything is explained in details.
Couroutines: These are just regular functions with the async keyword at the beginning and is awaitable. Coroutines can execute other coroutines inside it with the await keyword. We will create these to control the flow of asynchronous code.
Await: It’s a keyword used to give control back to the event loop and waits until a task is done or has returned with a result.
Event loop: When running a coroutine where an await keyword is encountered the event loop gets the control. It then checks if a particular awaited task is done. If it isn’t, it moves on to check for other tasks that are done or has returned some result. This is when the loop gives the control back to the coroutine to execute rest of the code inside it until another await is encountered.
Two coroutines running together
import asyncio
TIMES = 5
async def coro1():
for _ in range(TIMES):
await asyncio.sleep(1)
print("coro1()")
async def coro2():
for _ in range(TIMES):
await asyncio.sleep(2)
print ("coro2()")
async def main():
task1 = asyncio.create_task(coro1())
task2 = asyncio.create_task(coro2())
await task1
await task2
asyncio.run(main())
Output:
coro1()
coro2()
coro1()
coro1()
coro2()
coro1()
coro1()
coro2()
coro2()
coro2()
You can run the code as it is. The coroutine coro1() has run 5 times once every second. And the coroutine coro2() has run 5 times every 2 seconds. The part where we awaited asyncio.sleep() is where the event loop recieved control and the coroutine stopped and waited until the event loop gave the control back after the time was done. And in the mean time gave control to the other coroutine. And the cycle repeated for each iteration.
Please note that there’s no guarantee what coroutine will run first when they both execute simultaneously at the same time. This is when the infamous “race condition” occur if one of the coroutines depend on the other somehow.
Although it seems like task 2 should run after task 1 is done, this is not the case. Both tasks have already started the moment we called asyncio.create_task() . We were merely awaiting for them to finish or else the program would have exited immedietly.
Note: If you know for sure how long your coroutines should run you can even omit the await task1 and await task2 . Instead prevent the program from exiting by adding await asyncio.sleep(YOUR_TIME_OUT_VALUE) in its place.
To manually cancel the tasks before they finish you can call task1.cancel() or task2.cancel().
Turning Blocking code into non-blocking awaitable code
import asyncio
TIMES = 5
async def coro1():
for _ in range(TIMES):
await asyncio.sleep(1)
print("coro1()")
async def coro2():
for _ in range(TIMES):
await asyncio.sleep(2)
print ("coro2()")
async def main():
task1 = asyncio.create_task(coro1())
task2 = asyncio.create_task(coro2())
task3 = asyncio.create_task(asyncio.to_thread(input, "Type something: "))
await task3
result = task3.result()
print("Your input is: ", result)
asyncio.run(main())
Output:
Type something: coro1()
coro2()
coro1()
coro1()
coro2()
coro1()
coro1()
coro2()
coro2()
coro2()
Hello world
Your input is: Hello world
There are a few interesting things going on here.