Introduction to Asynchronous Programming in Python

Skylar Johnson
5 min readDec 28, 2023

Synchronized programming is a concept familiar to almost all programming beginners because it is the type of code that executes one statement at a time. Asynchronous programming is the type of coding technique in which instructions are executed simultaneously. This means that they are independent of the main flow of the program and run in the background, without the need to wait for the previous task or instructions to complete.

This can be useful in a situation where, for example, you have some unrelated tasks that take a long time to complete, potentially blocking the main flow of the program, and whose outputs do not depend on each other . In these cases, asynchronous programming can not only help you save time but also use resources optimally and efficiently.

When do we need asynchronous programming?

Imagine a traditional web scraping program that needs to open thousands of network connections. You can open a connection, get the results, and then move on to the next one iteratively. However, this can increase program latency because a lot of time must be spent opening a connection and waiting for the previous instruction to complete.

On the other hand, asynchronous programming can help you open and switch thousands of connections at once. based on the connection when they complete their execution and return results. So it basically sends the request and moves on to the next instruction without waiting for execution to complete, and continues like this until all instructions have been executed.

Asynchronous programming can be useful in situations like the following:

  • Executing the program and returning the results takes a long time.
  • Or activities that may require multiple input or output operations to be performed simultaneously.

Before diving into Async IO, let’s quickly discuss the differences between the more confusing terms asynchronous programming concepts: concurrency, parallelism, threading, and asynchronous I/O:

  • Parallelism: involves executing multiple tasks at the same time (simultaneously). It is suitable for CPU-intensive tasks, and multiprocessing is an example of parallelism. Learn more about multiprocessing. Multiprocessing and multithreading in Python here.
  • Concurrency: Concurrency can be defined as a much broader form of parallelism in which multiple tasks can be performed in an overlapping manner.
  • Thread: A thread is an independent flow of execution. Basically, you can think of it as a single lightweight component of a process that can run in parallel. In a process there can be multiple threads that share the same memory space, that is, they share the code to execute and the variables declared in the program. Multithreading is suitable for I/O related tasks.
  • Async IO — Async IO is a single-threaded, single-process implementation that gives the appearance of parallelism.

After learning the difference between these often confusing terms, let’s dive deeper into Async IO. In this article, you will learn what Async IO is, its components, and a basic Python implementation to help you. to facilitate startup.

Async IO in Python Programming

Python3 natively supports asynchronous programming with Async IO, a Python library that allows you to write code simultaneously using the asynchronous syntax / await.

It is also the core of many other asynchronous programming frameworks that provide high-performance network, server web, distributed task queues and database connection, libraries, etc.

Let’s explore the different components of Async IO programming to understand how it works.

Asynchronous IO Programming Components

  • Coroutines: These are the functions that schedule the execution of tasks.
  • Activities: used to schedule routines at the same time.
  • Event loop: This is the basic mechanism that runs coroutines until they complete. You can think of it as a simple while loop that monitors a routine and continuously receives feedback on what is idle and looks for tasks that can be executed in the meantime. In Python, only one event loop can run at a time.
  • Futures: These are the results of executing a coroutine. This too could be an exception.

Now let’s see how these components interact to execute asynchronous code running in a single thread.

It looks something like this -

  • The event loop runs in a thread.
  • Gets a task from the queue.
  • Each task calls the next step in a coroutine
  • When the current routine calls another routine, the current routine is paused and a context switch occurs so that the context of the called routine is loaded and the context of the current routine is saved.
  • If the current routine encounters a blocking code (I/O or suspend), the current routine is paused and control returns to the event loop.The event loop then fetches the next task in the queue and starts executing the previous steps again until the queue is empty.
  • The event loop then returns to activity 1, where it started.

How does Async IO work?

Async IO uses two keywords — async and wait . Any function written as async def is a coroutine, for example main()

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 it in a asyncio.run() Function that acts as an entry point for the async function, starts the event loop, and executes the coroutine. The keyword await indicates that the coroutine is waiting for the result and returns control to the event loop.

Now, to run the routines simultaneously, we need to define the tasks. We use asyncio.create_task() to schedule a coroutine to execute in the event loop.

For example, the following code snippet shows 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.

Conclusion

In this article, we discuss asynchronous programming in Python using the built-in Async IO module. We also learned the basic components of Async IO, such as coroutines, tasks, and futures, and how to use them. in a simple program implemented to achieve parallelism.

--

--

Skylar Johnson

I'm a Web developer who is always looking to learn more and eat too much chocolate. https://www.thetravelocity.com