asyncio
trio
Python concurrency
Hettinger's example
asynchronous programming

async trio way to solve Hettinger's example

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

When people ask for the “Trio way” to solve an asyncio example from Raymond Hettinger or similar teaching material, they usually want more than a syntax conversion. Trio encourages structured concurrency, which means tasks should be started, supervised, and cancelled in a clear scope rather than launched ad hoc and forgotten.

The Core Trio Idea: Use a Nursery

The central Trio concept is the nursery. A nursery is the scope that owns concurrently running child tasks.

python
1import trio
2
3async def worker(name: str, delay: float) -> None:
4    print(f"{name} starting")
5    await trio.sleep(delay)
6    print(f"{name} done")
7
8async def main() -> None:
9    async with trio.open_nursery() as nursery:
10        nursery.start_soon(worker, "a", 1)
11        nursery.start_soon(worker, "b", 2)
12
13trio.run(main)

This is the Trio replacement for many “schedule several tasks and wait for them” patterns from asyncio examples.

Why This Differs From Old asyncio Style

Traditional asyncio teaching often used patterns like:

  • create several tasks
  • gather them later
  • hope they are all cleaned up correctly

Trio makes the ownership relationship explicit. If one child task fails, the nursery handles cancellation of sibling tasks and propagates the error in a controlled way. That is one reason Trio code often feels simpler even when it is strict.

Rewriting a Typical Concurrent Example

A common teaching pattern is “launch several independent jobs and wait until all finish.” In Trio, the answer is almost always a nursery.

python
1import trio
2
3async def fetch(number: int) -> None:
4    await trio.sleep(0.5)
5    print(f"fetched {number}")
6
7async def main() -> None:
8    async with trio.open_nursery() as nursery:
9        for i in range(5):
10            nursery.start_soon(fetch, i)
11
12trio.run(main)

No separate “join” call is needed. Exiting the nursery means all children have either completed or been cancelled.

Timeouts and Cancellation Are Also Scoped

Trio prefers cancellation scopes instead of loosely scattered timeout logic.

python
1import trio
2
3async def slow_job() -> None:
4    await trio.sleep(5)
5
6async def main() -> None:
7    with trio.move_on_after(1) as scope:
8        await slow_job()
9    if scope.cancelled_caught:
10        print("timed out")
11
12trio.run(main)

That fits the same structured style: work and cancellation live together in one scope.

When You Need Results Back

If several Trio tasks need to produce results, use shared state carefully or channel-like primitives rather than trying to imitate gather mechanically.

python
1import trio
2
3async def worker(number: int, results: list[int]) -> None:
4    await trio.sleep(0.1)
5    results.append(number * number)
6
7async def main() -> None:
8    results = []
9    async with trio.open_nursery() as nursery:
10        for i in range(4):
11            nursery.start_soon(worker, i, results)
12    print(sorted(results))
13
14trio.run(main)

This is enough for simple cases. For more complex pipelines, Trio's memory channels are often a better fit than shared mutable lists.

The Real Migration Mindset

The main mistake in moving from asyncio examples to Trio is trying to translate API calls one for one. Trio is not just “different function names for the same architecture.” It is a different concurrency model centered on structured task ownership.

If an old example uses loose background task creation, the Trio version should usually ask: who owns these tasks, when do they end, and what happens if one fails?

Common Pitfalls

The most common mistake is trying to recreate asyncio.create_task() patterns everywhere instead of embracing nurseries.

Another mistake is treating cancellation as an afterthought. In Trio, cancellation is designed into the task structure, not bolted on afterward.

Developers also often port syntax without rethinking task ownership, which defeats the main benefit of Trio.

Summary

  • The Trio answer to many concurrent asyncio examples is “use a nursery.”
  • Nurseries make task ownership and shutdown explicit.
  • Cancellation and timeouts are scoped rather than scattered.
  • Do not translate old asyncio examples mechanically line by line.
  • Structured concurrency is the real Trio idea, not just a new API surface.

Course illustration
Course illustration

All Rights Reserved.