Async Programming in Python: using asyncio and trio

Introducion

As modern applications become more network-heavy and I/O-bound, Python developers increasingly rely on asynchronous programming to improve performance and responsiveness. Two powerful libraries—asyncio and Trio—provide different approaches to concurrency, with distinct philosophies and APIs. This guide explains how they work, where they excel, and how to choose the right tool for your next Python project.

What Is Asynchronous Programming?

Asynchronous programming allows your application to perform multiple operations without blocking the main thread. Instead of waiting for tasks like API calls, file reads, or database queries, the runtime suspends tasks until they are ready and switches to other tasks meanwhile.

This results in:

  • Better performance for I/O-bound workloads
  • Improved throughput
  • More efficient resource usage
  • Faster user experiences in networked applications

asyncio: Python’s Built-In Async Framework

asyncio is Python’s official async library, included in the standard library since Python 3.4. It provides the core primitives for coroutines, tasks, events loops, and async I/O.

Key Concepts in asyncio

1. Event Loop

The engine that schedules and runs asynchronous tasks.

import asyncio

async def main():
    print("Hello from asyncio")

asyncio.run(main())

2. Coroutines

Functions defined with async def that can be awaited.

async def fetch_data():
    await asyncio.sleep(1)
    return "done"

3. Tasks

Scheduled coroutines that run concurrently.

async def main():
    task1 = asyncio.create_task(fetch_data())
    task2 = asyncio.create_task(fetch_data())
    await task1
    await task2

4. Awaitables

Any object that can be used with await, including coroutines and asynchronous context managers.

Common Use Cases for asyncio

  • High-performance web servers
  • API clients
  • Web scraping at scale
  • Database clients (asyncpg, aioredis, etc.)
  • Real-time applications
  • Background workers

Since it’s part of standard Python, asyncio has the largest ecosystem and the broadest support across popular libraries.

Trio: A New, Safer Approach to Async in Python

Trio is an alternative async framework built around structured concurrency and developer ergonomics. Instead of giving you raw primitives with sharp edges, Trio provides a safer, more consistent environment for async programming.

Its philosophy focuses on:

  • Predictable task lifecycles
  • Better error handling
  • Simpler cancellation semantics
  • Eliminating callback-style pitfalls
  • A more Pythonic async programming experience

Trio’s Core Ideas

1. Structured Concurrency

Tasks run inside “nurseries,” which act as scoped containers.

import trio

async def child():
    await trio.sleep(1)
    print("child done")

async def main():
    async with trio.open_nursery() as nursery:
        nursery.start_soon(child)

trio.run(main)

Nurseries ensure:

  • Children exit before parents
  • Exceptions propagate sanely
  • No orphan tasks continue running silently

2. Built-In Safety

Trio prevents many common asyncio mistakes:

  • Forgetting to handle task exceptions
  • Leaving tasks running in the background
  • Complex cancellation behavior
  • Non-deterministic shutdowns

3. Simpler API

Trio’s API is intentionally minimal. Instead of juggling event loop objects and task scheduling methods, you work entirely with structured scopes.

When To Use Trio

Trio is ideal when:

  • You want readability and maintainability
  • Safety and correctness matter more than raw ecosystem size
  • You’re building complex async workflows
  • You prefer structured concurrency models

Libraries like AnyIO even allow you to write framework-agnostic async code that works for both asyncio and Trio.

asyncio vs Trio: Side-by-Side Comparison

FeatureasyncioTrio
Included in standard libraryYesNo
Ecosystem supportExtensiveGrowing
Concurrency modelUnstructuredStructured
SafetyManualBuilt-in
Error handlingMore manualMore robust
ComplexityMedium–HighLow–Medium
Best forNetworking, servers, production appsResearch, complex async logic, safer concurrency

Real-World Example: Async HTTP Requests

Using asyncio

import asyncio
import aiohttp

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

async def main():
    urls = [
        "https://example.com",
        "https://python.org",
    ]
    tasks = [fetch(url) for url in urls]
    results = await asyncio.gather(*tasks)
    print(results)

asyncio.run(main())

Using Trio

import httpx
import trio

async def fetch(url):
    async with httpx.AsyncClient() as client:
        res = await client.get(url)
        return res.text

async def main():
    urls = [
        "https://example.com",
        "https://python.org",
    ]
    
    async with trio.open_nursery() as nursery:
        results = []
        for url in urls:
            nursery.start_soon(lambda u=url: results.append(trio.run(fetch, u)))

trio.run(main)

Trio code is more structured and tends to avoid hidden pitfalls caused by orphaned tasks or unhandled exceptions.

Which One Should You Use?

Choose asyncio when:

  • You rely on mainstream async libraries
  • You need maximum compatibility
  • You’re building web servers or API clients
  • You want async support out of the box

Choose Trio when:

  • You value safety and correctness
  • You want clean, maintainable async code
  • You prefer structured concurrency
  • You’re working on complex async workflows

Conclusion

Python’s async ecosystem is rich and evolving. asyncio provides a powerful, standard foundation for async programming and integrates deeply with web frameworks and database clients. Trio brings a cleaner, safer, and more structured approach that can significantly improve the developer experience.

Both tools are excellent. The right choice depends on your project’s constraints, your team’s preferences, and your long-term maintainability goals.

Leave a Comment

Scroll to Top