Python - Async Programming in Python (async / await)

Async programming in Python is a way to write code that can handle multiple tasks seemingly at the same time without using multiple threads or processes. It is especially useful for tasks that spend a lot of time waiting, such as network requests, file operations, or database queries.


1. The Core Idea

In normal (synchronous) Python code, tasks run one after another. If one task is waiting (for example, downloading data), the entire program pauses.

Async programming allows the program to continue doing other work while waiting. This improves efficiency, especially in I/O-bound programs.


2. Key Concepts

a) Coroutine

A coroutine is a special type of function defined using async def. It can pause and resume its execution.

Example:

async def greet():
    print("Hello")

Calling greet() does not run it immediately. It returns a coroutine object.


b) await Keyword

The await keyword is used inside an async function to pause execution until another async operation completes.

import asyncio

async def task():
    print("Start")
    await asyncio.sleep(2)
    print("End")

Here, await asyncio.sleep(2) pauses the coroutine for 2 seconds without blocking the entire program.


c) Event Loop

The event loop is the engine that runs and manages all async tasks. It schedules and switches between them.

Example:

import asyncio

async def main():
    print("Running async code")

asyncio.run(main())

asyncio.run() starts the event loop and executes the coroutine.


3. Running Multiple Tasks Concurrently

Async programming allows multiple tasks to run concurrently.

Example:

import asyncio

async def task1():
    print("Task 1 started")
    await asyncio.sleep(2)
    print("Task 1 finished")

async def task2():
    print("Task 2 started")
    await asyncio.sleep(1)
    print("Task 2 finished")

async def main():
    await asyncio.gather(task1(), task2())

asyncio.run(main())

Explanation:

  • Both tasks start almost at the same time

  • Task 2 finishes earlier because it waits less

  • Total execution time is reduced compared to sequential execution


4. Async vs Multithreading

Async programming:

  • Uses a single thread

  • Controlled by an event loop

  • Best for I/O-bound tasks

Multithreading:

  • Uses multiple threads

  • Can run tasks in parallel (limited by Python’s GIL)

  • Better for some blocking operations


5. When to Use Async Programming

Use async when:

  • Making API calls

  • Working with web servers

  • Handling many users at once

  • Performing database queries

  • Reading/writing files frequently

Avoid async when:

  • Tasks are CPU-intensive (like heavy calculations)

  • Simpler synchronous code is sufficient


6. Real-World Example (Fetching Data)

import asyncio

async def fetch_data():
    print("Fetching data...")
    await asyncio.sleep(2)
    print("Data received")

async def main():
    await fetch_data()

asyncio.run(main())

This simulates a network request. While waiting, the program can handle other tasks.


7. Advantages

  • Efficient use of time during waiting operations

  • Better performance in web and network applications

  • Scales well for handling many concurrent operations


8. Limitations

  • More complex to understand than normal code

  • Debugging can be harder

  • Not useful for CPU-heavy work

  • Requires async-compatible libraries


9. Important Libraries

  • asyncio (built-in)

  • aiohttp for async HTTP requests

  • aiomysql, asyncpg for databases


Summary

Async programming in Python allows you to write efficient programs that can handle multiple waiting tasks without blocking execution. It is built around coroutines, the await keyword, and an event loop, making it ideal for modern applications like web servers and APIs.