Python Trio Concurrency — Core Concepts
What Makes Trio Different
Trio is a Python library for async/await concurrency, but it takes a fundamentally different approach from asyncio. Its core philosophy is structured concurrency — the idea that concurrent tasks should follow the same nesting rules as regular function calls.
In asyncio, you can fire off a task with create_task() and it floats around independently. You might forget to await it, it might crash silently, or it might outlive the function that created it. Trio makes this impossible by design.
Nurseries: The Core Idea
In Trio, you can only spawn tasks inside a nursery. The nursery block doesn’t exit until all tasks inside it are finished:
import trio
async def fetch(url):
await trio.sleep(1) # simulate network call
print(f"Done: {url}")
async def main():
async with trio.open_nursery() as nursery:
nursery.start_soon(fetch, "url-a")
nursery.start_soon(fetch, "url-b")
nursery.start_soon(fetch, "url-c")
# Only reaches here after ALL three fetches complete
print("All done!")
trio.run(main)
The async with block is the key. It creates a scope, and tasks spawned in that scope cannot outlive it. This is “structured” — task lifetimes mirror code structure.
Error Handling That Actually Works
If any task in a nursery raises an exception, Trio:
- Cancels all other tasks in that nursery
- Waits for them to finish cancelling
- Raises the exception(s) to the nursery’s parent
If multiple tasks fail, Trio collects all exceptions into an ExceptionGroup (Python 3.11+):
async def main():
try:
async with trio.open_nursery() as nursery:
nursery.start_soon(task_that_crashes)
nursery.start_soon(task_that_also_crashes)
except* ValueError as eg:
print(f"Got {len(eg.exceptions)} ValueErrors")
Compare this with asyncio, where a forgotten task’s exception might just print a warning to stderr that you never see.
Cancellation Scopes
Trio has first-class support for timeouts and cancellation via cancel scopes:
async def fetch_with_timeout():
with trio.move_on_after(5) as cancel_scope:
result = await slow_network_call()
if cancel_scope.cancelled_caught:
print("Timed out, using fallback")
result = default_value
move_on_after cancels the block after the deadline and continues execution. fail_after raises trio.Cancelled instead. Both are explicit — you always know where cancellation can happen.
Checkpoints
Trio requires every task to periodically hit a checkpoint — a point where it can be cancelled or another task can run. All I/O operations and await trio.sleep() are checkpoints. If you have CPU-heavy code, you insert manual checkpoints:
async def cpu_work():
for i in range(1_000_000):
do_something(i)
if i % 1000 == 0:
await trio.sleep(0) # checkpoint
This guarantee means Trio can always cancel a task promptly. No task can hog the event loop indefinitely without Trio warning you.
When to Choose Trio over asyncio
Choose Trio when:
- You want strong guarantees about task lifecycle management
- Error handling in concurrent code is critical (financial systems, data pipelines)
- You value explicit cancellation and timeouts
- You’re starting a new project without asyncio ecosystem dependencies
Stick with asyncio when:
- You need libraries that only support asyncio (most of the Python async ecosystem)
- You’re adding async to an existing project
- Framework choice is constrained (FastAPI, aiohttp, etc.)
Common Misconception
“Trio is faster than asyncio.” Performance is comparable for most workloads. Trio’s advantage is safety, not speed. It prevents entire categories of concurrency bugs at the cost of slightly more structured code.
One thing to remember: Trio’s nurseries enforce a simple rule — every task must be tracked, and a parent scope never exits until all its child tasks are done or cancelled. This eliminates orphaned tasks and silent failures.
See Also
- Python Actor Model Why treating each piece of your program like a person with their own mailbox makes concurrency way less scary.
- Python Aiocache Caching aiocache remembers expensive answers so your async Python app doesn't waste time asking the same question twice.
- Python Aiofiles Async Io aiofiles lets your async Python program read and write files without freezing — because normal file operations secretly block everything.
- Python Aiohttp Understand Aiohttp through an everyday analogy so Python behavior feels intuitive, not random.
- Python Anyio Portability AnyIO lets your async Python code work with any async library — write once, run on asyncio or Trio without changes.