Async Await in Python — Asyncio Deep Dive (76/100 Days of Python)

Martin Mirakyan
4 min readMar 18, 2023
Day 76 of the “100 Days of Python” blog post series covering asyncio

Asynchronous programming has become increasingly popular in recent years due to its ability to improve the performance and scalability of applications. Python has its own native support for asynchronous programming via the asyncio module. In this tutorial, we will take a detailed look at asyncio in Python and explore its features and capabilities.

What is asyncio?

Asyncio is a Python library for asynchronous programming, which provides the infrastructure for writing single-threaded concurrent code using coroutines, event loops, and non-blocking I/O. Asyncio was introduced in Python 3.4 as a way to write asynchronous code in a more intuitive and natural way than with traditional callback-based approaches.

Asyncio enables programmers to write code that is both concurrent and asynchronous, without requiring the use of multiple threads or processes. It uses coroutines, which are lightweight, stackless functions that can be suspended and resumed at specific points in their execution. This makes it possible to write code that appears to run concurrently, even though it’s really running in a single thread.

Async/await syntax

One of the key features of asyncio is the async/await syntax. The async keyword is used to define a coroutine function, which can be suspended and resumed at specific points during its execution. The await keyword is used to indicate that a coroutine function should pause execution until a certain event occurs, such as the completion of an I/O operation:

import asyncio


async def hello_world():
print('Hello')
await asyncio.sleep(1)
print('World')


asyncio.run(hello_world())

In this example, the hello_world coroutine function prints “Hello”, pauses for one second using the asyncio.sleep() function (which is a coroutine itself), and then prints “World”.

Note that the asyncio.run() function is used to run the coroutine function. This function creates an event loop, runs the coroutine until it completes, and then closes the event loop.

Event loops

At the heart of asyncio is the event loop, which is responsible for managing the execution of coroutines and handling I/O operations. The event loop is an infinite loop that waits for events to occur and dispatches them to the appropriate coroutine functions:

import asyncio


async def my_coroutine():
print('Coroutine started')
await asyncio.sleep(1)
print('Coroutine finished')


loop = asyncio.get_event_loop()
loop.run_until_complete(my_coroutine())
loop.close()

In this example, we get an event loop using the asyncio.get_event_loop() function. We then use the run_until_complete() method of the event loop to run our coroutine function. Finally, we close the event loop using the close() method.

Tasks

Tasks are another important concept in asyncio. A task is a higher-level abstraction on top of a coroutine function that provides additional functionality, such as cancellation and exception handling:

import asyncio


async def my_coroutine():
print('Coroutine started')
await asyncio.sleep(1)
print('Coroutine finished')


async def main():
task = asyncio.create_task(my_coroutine())
await task


asyncio.run(main())

Here we create a task using the asyncio.create_task() function, passing in our coroutine function as the argument. We then await the completion of the task using the await keyword.

Use Cases of asyncio

Asyncio is a powerful tool for building highly scalable and efficient applications in Python. It can be used for a variety of purposes and is well-suited for applications that require concurrent and asynchronous programming.

Networking

One of the most common use cases for asyncio is networking. Asyncio makes it easy to write asynchronous networking code, allowing you to efficiently handle multiple network connections without the need for multiple threads or processes. This makes it ideal for building high-performance network servers and clients.

For example, an HTTP server built with asyncio can handle multiple simultaneous requests using a single thread, making it much more efficient than traditional servers that use multiple threads or processes.

Web scraping

Web scraping is another area where asyncio can be highly effective. Asyncio can be used to asynchronously fetch and process data from multiple web pages in parallel, allowing you to scrape data from multiple sources quickly and efficiently.

With asyncio, you can create a series of coroutines that fetch web pages and parse their content, allowing you to scrape data from multiple sources simultaneously. This makes web scraping with asyncio much faster and more efficient than traditional approaches.

Databases

Asyncio is also becoming increasingly popular for database programming. With asyncio, you can create asynchronous database operations that can run concurrently, allowing you to handle multiple database connections in a single thread. This can greatly improve the performance of database operations, especially when working with large datasets or complex queries.

One example of a popular Python library that has introduced asyncio support for database operations is SQLAlchemy. Starting from version 2.0, SQLAlchemy introduced support for asyncio, allowing you to write asynchronous database operations using the same declarative syntax used for synchronous operations. This makes it easy to build high-performance, asynchronous database applications that can handle multiple connections concurrently.

What’s next?

--

--

Martin Mirakyan

Software Engineer | Machine Learning | Founder of Profound Academy (https://profound.academy)