Synchronous programming is a concept familiar to almost anyone starting to code as it’s the kind of code that is executed one instruction at a time. Whereas asynchronous programming is the kind of coding technique where the instructions are executed concurrently. This means that they are independent of the main program flow and are executed in the background without waiting for the completion of the previous task or instruction.
This could be helpful in a situation where let’s say you have certain independent tasks which take a lot of time to execute and might block the main flow of the program and their outputs are not dependent on each other. In such instances, asynchronous programming could not just help you to save some time but also help you in making optimal and efficient use of the resources.
Consider a traditional web scraping program that needs to open thousands of network connections. It could open one connection, fetch the results, and then move to the next one iteratively but this could increase the latency of the program as it has to spend a lot of time to open a connection and then wait for the previous instruction to be finished.
On the other hand, asynchronous programming could help you open thousands of connections at once and lets you swap among each connection based as they finish their execution and return the results, thus, basically, it will send the request and move on to the next instruction without waiting for the execution to finish and it continues like this until all the instructions have been executed.
Asynchronous programming can be helpful in situations such as the following:
Before we dive deeper into Async IO, let’s discuss the differences between the most confusing terms in asynchronous programming - Parallelism, Concurrency, Threading, and Async IO in brief:
Having learned the difference between these commonly confused terms, let’s dive deeper into Async IO. In this article, we’ll be learning what is Async IO, its components, and a basic implementation in Python to get you started.
Python3 natively supports asynchronous programming with Async IO, which is a Python library that enables to write of concurrent code by using async/await syntax.
It is also the core of many other asynchronous programming frameworks that provide high-performance networks, web servers, distributed task queues, database connection libraries, etc.
Let’s explore the different components of Async IO Programming to understand how it works.
1. Coroutines: These are the functions that schedule the execution of the tasks.
2. Tasks: These are used to schedule coroutines concurrently.
3. Event Loop: This is the central mechanism that executes the coroutines until they are complete. It can be imagined as a simple while loop that monitors a coroutine and takes continuous feedback on what’s idle and looks around for the tasks that can be executed in the meantime. In Python, only one event loop can run at a time.
4. Futures: These are the results of the execution of a coroutine. This might be an exception as well.
Read more about these components here.
Now, let’s see how these components work together to execute asynchronous code running in a single thread.
It looks something like this -
Async IO uses two keywords - async
and await
. Any function that is written as async def
is a coroutine, for example, main()
below:
import asyncio
async def main():
print('Hello ...')
await asyncio.sleep(1)
print('... World!')
asyncio.run(main())
# Hello ...
# ... World!
The most common way to run a coroutine is to wrap the coroutine in a asyncio.run()
function that acts as an entry point to the async function and starts the event loop and runs the coroutine. The await
keyword indicates to await the result of the coroutine and passes the control back to the event loop.
Now, to run the coroutines concurrently, we’ll need to define tasks. We use asyncio.create_task()
to schedule a coroutine for execution in the event loop.
Notice that the task is different from the await
keyword that blocks the entire coroutine until the operation completes with a result.
For example, the following code snippet illustrates how to create tasks that schedule and execute a coroutine(call_api()
) in this case:
import asyncio
import time
async def call_api(message, result=1000, delay=3):
print(message)
await asyncio.sleep(delay)
return result
async def main():
start = time.perf_counter()
task_1 = asyncio.create_task(
call_api('Get stock price of GOOG...', 300)
)
task_2 = asyncio.create_task(
call_api('Get stock price of APPL...', 300)
)
price = await task_1
print(price)
price = await task_2
print(price)
end = time.perf_counter()
print(f'It took {round(end-start,0)} second(s) to complete.')
asyncio.run(main())
The output will be something like this:
Get stock price of GOOG...
Get stock price of APPL...
300
300
It took 3.0 second(s) to complete.
In this article, we discussed asynchronous programming in Python with the built-in Async IO module. We also learned the basic components of Async IO such as coroutines, tasks, and futures, and how to implement these in a simple program to attain concurrency.
This was all for this article. I hope it was helpful.