Glossary

Asyncio

Most Python code executes from top to bottom, one step at a time.

That model breaks when your program has to wait on something, like a network call or a file read.

asyncio is Python’s answer. It uses an event loop to run multiple tasks on a single thread. No threads. No processes. Just tasks that pause and resume with async and await.

What Is asyncio

asyncio is a standard library in Python that lets you write asynchronous code. It helps you manage concurrency without using threads or multiple processes.

It’s designed for I/O-bound operations: reading files, making HTTP requests, or waiting for database results. When one task waits, another starts. All in one thread.

At the core of asyncio:

  • async def defines a coroutine function
  • await pauses a coroutine until the awaited task finishes
  • The event loop runs all scheduled tasks
  • Tasks and Futures let you coordinate execution

You can:

  • Use asyncio.run() to launch the main coroutine
  • Use asyncio.gather() to run tasks concurrently
  • Use create_task() to schedule background work
  • Use TaskGroup to manage related tasks with better structure

Event Loop and Coroutines

The event loop is the engine behind all of it. It runs coroutines, switches between tasks, and resumes work once each pause is over.

Define a coroutine:

async def main():
    await asyncio.sleep(1)

But it won't run until you execute:

 asyncio.run(main()

That starts the event loop and drives everything to completion.

Calling a coroutine function returns a coroutine object. It doesn't execute by default. You must await it or schedule it with create_task().

Example: Concurrent Tasks

import asyncio

async def say_after(delay, what):
    await asyncio.sleep(delay)
    print(what)

async def main():
    await asyncio.gather(
        say_after(1, 'hello'),
        say_after(2, 'world')
    )

asyncio.run(main())

This prints "hello" after 1 second, then "world" after 2 seconds. Because they run together, total runtime is 2 seconds. If run sequentially, it would take 3.

Coroutine Objects vs Coroutine Functions

When you call a coroutine function, it returns a coroutine object. It does nothing until you await it.


coro = main()  # coroutine object
asyncio.run(coro)  # executes it

Using create_task()

async def delayed():
    await asyncio.sleep(1)
    print("Done")

async def main():
    task = asyncio.create_task(delayed())
    print("Task scheduled")
    await task

asyncio.run(main())

create_task() schedules the coroutine to run soon. The event loop takes care of when.

TaskGroup for Structured Concurrency

TaskGroup makes it easier to manage multiple tasks. It waits for all of them to complete. If one fails, it cancels the others.


import asyncio

async def job(n, delay):
    await asyncio.sleep(delay)
    print(f"Job {n} done after {delay}s")

async def main():
    async with asyncio.TaskGroup() as tg:
        tg.create_task(job(1, 1))
        tg.create_task(job(2, 2))
        tg.create_task(job(3, 0.5))
    print("All jobs completed")

asyncio.run(main())

The async with block ensures all tasks are done or canceled before it exits.

Timeouts and Cancellation

Tasks can be canceled by calling cancel() or by using asyncio.timeout():

import asyncio

async def cleanup_task():
    try:
        while True:
            print("Working...")
            await asyncio.sleep(1)
    finally:
        print("Cleaning up before exit.")

async def main():
    task = asyncio.create_task(cleanup_task())
    await asyncio.sleep(3)
    task.cancel()
    try:
        await task
    except asyncio.CancelledError:
        print("Task was cancelled")

asyncio.run(main())

Use asyncio.timeout() to limit duration:

async def main():
    try:
        async with asyncio.timeout(2):
            await asyncio.sleep(5)
    except TimeoutError:
        print("Operation timed out")

Integration with Files, HTTP, and CPU Work

Use async libraries to avoid blocking the event loop.

Async File I/O

import aiofiles
import asyncio

async def write_log(message):
    async with aiofiles.open('log.txt', mode='a') as f:
        await f.write(message + '\n')

async def main():
    await asyncio.gather(
        write_log("Started A"),
        write_log("Started B")
    )

asyncio.run(main())

Async HTTP Requests

import aiohttp
import asyncio

async def fetch(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as resp:
            return await resp.text()

async def main():
    urls = ['https://example.com', 'https://httpbin.org']
    pages = await asyncio.gather(*(fetch(url) for url in urls))
    for content in pages:
        print(len(content))

asyncio.run(main())

CPU-Bound Work

Use run_in_executor() to handle blocking or CPU-heavy code.

import asyncio
import time

def blocking_task():
    time.sleep(2)
    return "done"

async def main():
    loop = asyncio.get_running_loop()
    result = await loop.run_in_executor(None, blocking_task)
    print(result)

asyncio.run(main())

For more parallelism, use ProcessPoolExecutor.

Thread Safety

asyncio is single-threaded. You don’t need to worry about locks unless you add threads yourself.

If coroutines share state, use asyncio.Lock :

lock = asyncio.Lock()

async def update():
    async with lock:
        # modify shared state
        ...

Or use asyncio.Queue :

queue = asyncio.Queue()

async def producer():
    await queue.put("data")

async def consumer():
    item = await queue.get()
    print("Received:", item)

I/O vs CPU: Know the Difference

Task Type Solution
REST API call I/O-bound asyncio + aiohttp
Read/write file I/O-bound aiofiles
Parse large JSON CPU-bound run_in_executor()
Web scraping I/O-bound asyncio.gather()
ML model training CPU-bound multiprocessing

Use asyncio when waiting. Use processes when computing.

Mixing Async and Blocking Code

Wrap sync functions with run_in_executor() :

async def run_blocking():
    loop = asyncio.get_running_loop()
    return await loop.run_in_executor(None, blocking_function)

Never run blocking code directly inside an async def . It will freeze the loop.

FAQ

What is asyncio in Python?

A library to write concurrent, asynchronous code using async and await . It runs in a single thread using an event loop.

Is asyncio multi-threaded?

No. It uses one thread with cooperative multitasking.

How does it compare to threading or multiprocessing?

  • asyncio: single-threaded, best for I/O-bound work
  • threading: multiple threads, but harder to manage
  • multiprocessing: best for CPU-heavy tasks

When should I use asyncio?

When your code waits on network, files, or database responses. It’s efficient and avoids thread overhead.

What is a coroutine object?

It’s what you get when you call an async def function. You must await or schedule it to run.

What’s the difference between await, gather, and create_task?

  • await: waits for a coroutine to finish
  • gather: runs several coroutines and waits for all
  • create_task: schedules a coroutine to run soon

What is a Future?

A placeholder for a result that’s not ready. Managed by asyncio.

Why use TaskGroup?

To manage a group of tasks with one structure. If one fails, the others are canceled. It simplifies cleanup.

Does asyncio support timeouts and canceling?

Yes. Use task.cancel() or asyncio.timeout() and handle with try/finally.

Can I run blocking code in asyncio?

Yes, but use run_in_executor() to avoid freezing the loop.

Is asyncio good for CPU-bound tasks?

No. Use multiprocessing or a thread pool.

What libraries should I use?

  • aiofiles for files
  • aiohttp for HTTP
  • run_in_executor() for sync code

Is asyncio stable?

Yes. It’s widely used in production systems and actively maintained.

What Python version should I use?

Python 3.7 or newer. That gives you [asyncio.run](<http://asyncio.run/>)(), create_task(), and TaskGroup.

Summary

asyncio is Python’s built-in tool for writing fast, non-blocking programs.

It is not a thread-based system. It runs an event loop, pauses tasks when they wait, and resumes them when ready. One thread. Many tasks. Maximum efficiency for I/O-heavy workloads.

You’ve learned how to use async def, await, gather, and create_task. You’ve seen TaskGroup for coordination, and run_in_executor() for legacy or CPU-bound work. You know when to use locks or queues. You know the right tools for files, HTTP, and long-running functions.

Use asyncio when your code waits.

Keep blocking code isolated.

Structure your app with clear, predictable task flows.

If your program does a lot of waiting, asyncio makes it move.

A wide array of use-cases

Trusted by Fortune 1000 and High Growth Startups

Pool Parts TO GO LogoAthletic GreensVita Coco Logo

Discover how we can help your data into your most valuable asset.

We help businesses boost revenue, save time, and make smarter decisions with Data and AI