Skip to main content

How to write concurrent Python with asyncio

Write concurrent Python with asyncio: define coroutines, run the event loop, await I/O concurrently with gather, manage tasks and timeouts, and choose asyncio, threads, or processes.

Difficulty
Intermediate
Duration
50 minutes
Steps
6

How asyncio works

asyncio is Python's framework for concurrent I/O-bound code. It runs many tasks on a single thread using cooperative multitasking: a coroutine voluntarily yields control with await while it waits for I/O, letting other coroutines run. This is ideal for network calls and not for CPU-bound work, because Python's global interpreter lock means CPU work does not parallelize across threads.

Prerequisites

  • Python 3.10 or later
  • Comfort with functions and return values

Steps

1. Write a coroutine

Define a coroutine with async def. Calling it returns a coroutine object, not a result.

import asyncio

async def fetch(name):
    await asyncio.sleep(1)  # simulates I/O
    return f"done: {name}"

2. Run the event loop

asyncio.run starts the loop and runs a coroutine to completion.

result = asyncio.run(fetch("a"))

3. Await I/O concurrently with gather

Running awaits sequentially is slow. gather runs them concurrently and waits for all.

async def main():
    results = await asyncio.gather(fetch("a"), fetch("b"), fetch("c"))
    return results  # all three finish in about 1 second

4. Create and manage tasks

create_task schedules a coroutine to run in the background so you can do other work before awaiting it.

task = asyncio.create_task(fetch("bg"))
# ... other work ...
await task

5. Add timeouts and cancellation

Guard against hangs with a timeout, which cancels the operation if it runs too long.

async with asyncio.timeout(2):
    await slow_operation()

6. Choose asyncio, threads, or processes

Use asyncio for many concurrent I/O operations, threads for blocking libraries with no async support, and multiprocessing for CPU-bound work that must run in parallel.

Verification

Write a program that fetches three coroutines sequentially, then with gather, and time both. Confirm gather finishes in roughly the time of the slowest call, not the sum. Add a timeout to a deliberately slow coroutine and confirm it cancels.

Next Steps

Explore async context managers, asyncio.TaskGroup for structured concurrency, async HTTP clients, and running blocking code in an executor with run_in_executor.

Prerequisites

  • Python 3.10+ installed
  • Basic Python functions

Steps

  • 1
    Write a coroutine
  • 2
    Run the event loop
  • 3
    Await I/O concurrently with gather
  • 4
    Create and manage tasks
  • 5
    Add timeouts and cancellation
  • 6
    Choose asyncio, threads, or processes