Skip to main content Brad's PyNotes

Asyncio vs Anyio

TL;DR

anyio is an async library that runs on top of asyncio or Trio, providing a unified API with structured concurrency. It solves asyncio’s design limitations while offering backend portability.

Interesting!

Code written with anyio runs unmodified on either asyncio or Trio backends. This means you can switch async frameworks without changing a single line of application code.

Backend Abstraction

asyncio is Python’s built-in async framework, while anyio is a compatibility layer that works with multiple backends:

python code snippet start

# asyncio - you're locked into this backend
import asyncio

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

asyncio.run(main())

# anyio - works with asyncio OR trio
import anyio

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

anyio.run(main())  # Uses asyncio by default
anyio.run(main(), backend='trio')  # Or switch to trio

python code snippet end

Structured Concurrency

anyio brings Trio’s structured concurrency (task groups) to asyncio, providing better error handling and resource cleanup:

python code snippet start

# asyncio - tasks can outlive their scope
async def asyncio_way():
    task1 = asyncio.create_task(fetch_data())
    task2 = asyncio.create_task(process_data())
    # If one fails, the other keeps running

# anyio - structured task groups
async def anyio_way():
    async with anyio.create_task_group() as tg:
        tg.start_soon(fetch_data)
        tg.start_soon(process_data)
    # Both tasks guaranteed to complete or cancel together

python code snippet end

Better APIs

anyio provides cleaner interfaces for common operations:

python code snippet start

# asyncio UDP - requires Transport/Protocol pattern
class UDPProtocol:
    def datagram_received(self, data, addr):
        # Handle data in callback
        pass

# anyio UDP - async/await throughout
async with anyio.create_udp_socket() as socket:
    data, addr = await socket.receive()
    # Direct async/await, no callbacks

python code snippet end

anyio also adds features asyncio lacks: async file I/O, signal handling, and versatile stream abstractions.

When to Choose Which

Use asyncio when you need the standard library solution with no dependencies, or you’re working with asyncio-specific libraries.

Use anyio when you want structured concurrency, cleaner APIs, or need backend flexibility. It’s particularly valuable for library authors who want to support multiple async frameworks.

For a deeper dive into asyncio itself, see the asyncio module guide . The async/await syntax used by both libraries was introduced in PEP 492 . If you’re working with CPU-bound tasks instead, consider concurrent.futures .

Reference: anyio Documentation