As Python is single-threaded, having a command of asynchronous programming has become mandatory to utilize that single thread efficiently. This article explores the fundamentals of asynchronous programming and elaborates with code snippets to help you fully utilize the potential of concurrency in Python.
Before diving into the actual code, let’s first understand some basic terminologies and concepts, so you can remain on track throughout the whole article.
Understanding Asynchronous Programming:
Asynchronous programming is a technique designed to handle concurrent tasks without blocking the entire thread. Whenever any program waits for something, such as DB Call/Network Request, it allows other tasks to run meanwhile, which improves overall performance.
Coroutines:
Coroutines are functions that can be paused and resumed at specific intervals. Unlike traditional functions, coroutines allow non-blocking execution by enabling tasks to pause and give control back to the event loop. This makes it possible to perform other operations while waiting for time-consuming tasks, to complete.
Asyncio is a library in Python for writing asynchronous code. The
async
keyword is used to define coroutines. A coroutine can containawait
expressions, indicating points at which the coroutine can be paused, allowing other tasks to execute in the meantime.
Here’s a simple coroutine that can be defined using async
keyword:
import asyncio
async def my_coroutine():
print("Start")
await asyncio.sleep(2)
print("End")
asyncio.run(my_coroutine())
Why using asyncio.sleep()
then time.sleep()
?
You might be wondering, what’s the difference between time.sleep()
and asyncio.sleep()
. The time.sleep()
function is a synchronous method that pauses the execution of the entire program/thread for a specified duration. On the other hand, asyncio.sleep()
is part of the asyncio
library and is specifically designed for asynchronous code. It will not block the entire program/thread, and only pauses a coroutine for a specified duration, enabling other tasks to run concurrently.
Below is a simple example illustrating the difference:
import time
import asyncio
print("**** Synchronous sleep ****")
time.sleep(2)
print("This will be printed after 2 seconds, as entire thread was blocked")
async def coroutine_1():
print("Pausing coroutine 1 ...")
await asyncio.sleep(1)
print("After a pause, resuming coroutine 1 ...")
async def coroutine_2():
print("coroutine_2() is executed because coroutine_1() is on pause")
async def main():
await asyncio.gather(coroutine_1(), coroutine_2())
print("**** Asynchronous sleep ****")
asyncio.run(main())
In the synchronous example, the entire program is paused for 2 seconds during time.sleep(2)
, while in the asynchronous example, only the coroutine_1()
is paused during asyncio.sleep(2)
, allowing other asynchronous task coroutine_2()
to continue.
Here’s the output:
**** Synchronous sleep ****
This will be printed after 2 seconds, as entire thread was blocked
**** Asynchronous sleep ****
Pausing coroutine 1 ...
coroutine_2() is executed because coroutine_1() is on pause
After a pause, resuming coroutine 1 ...
Event Loop:
The event loop is responsible for scheduling and executing coroutines. It ensures the tasks progress without waiting for one another.
Note: An event loop is always required if you are working with asynchronous tasks.
You can create an event loop usingnew_event_loop()
, here’s the code:
import asyncio
async def any_async_task():
await asyncio.sleep(1)
loop = asyncio.new_event_loop()
loop.run_until_complete(any_async_task())
loop.close()
Power of asyncio.run(my_coroutine())
:
In the above paragraph, I have mentioned that an event loop is always mandatory to run asynchronous tasks, then why is our first program my_coroutine()
working without an event loop? The reason is when you call asyncio.run(my_coroutine())
, it automatically creates a new event loop, starts running the specified coroutine in that event loop, and then closes the event loop after completion.
Tasks:
Tasks represent the execution of a coroutine and are managed by the event loop. They allow you to run multiple coroutines concurrently.
import asyncio
async def task1():
await asyncio.sleep(1)
print("This will be printed after 1 second pause")
async def task2():
print("This will be printed immediately")
async def main():
await asyncio.gather(task1(), task2())
# Run the main function using asyncio.run
asyncio.run(main())
Here’s the output of the above program:
task2() will be executed immediately
task1() will be executed after 1 second pause
Futures:
Futures are placeholders for the result of asynchronous operations. They allow you to retrieve the outcome of a coroutine once it is completed.
import asyncio
async def my_coroutine():
await asyncio.sleep(1)
return "Any Async Output"
my_future = asyncio.Future()
my_future.set_result(asyncio.run(my_coroutine()))
result = my_future.result()
print(result)
Alternatively, you can also achieve the above result
by using the following simplified line of code:
result = asyncio.run(my_coroutine())
print(result)
Real-life Example - Making multiple HTTP Requests:
Now, let’s make multiple HTTP Requests asynchronously:
import aiohttp, asyncio
async def fetch_data(i, url):
print('Starting', i, url)
async with aiohttp.ClientSession() as session:
async with session.get(url):
print('Finished', i, url)
async def main():
urls = ["https://dev.to", "https://medium.com", "https://python.org"]
async_tasks = [fetch_data(i+1, url) for i, url in enumerate(urls)]
await asyncio.gather(*async_tasks)
asyncio.run(main())
The above program will print the following output, which shows that all 3 tasks ran almost independently without waiting for each other, and print Finished
when each one is completed:
Starting 1 https://dev.to
Starting 2 https://medium.com
Starting 3 https://python.org
Finished 2 https://medium.com
Finished 3 https://python.org
Finished 1 https://dev.to
Conclusion:
I tried to cover as many concepts related to asynchronous programming as possible. I will consistently put my efforts here on this platform. If you liked my content, then don't forget to appreciate it, it boosts my confidence.
Thanks for reading
Top comments (0)