anyio・Trio・asyncioの違いと使い分け|Python非同期ライブラリ比較
Pythonで非同期処理を書くとき、標準のasyncioのほかにTrioとanyioという選択肢がある。この3つは競合ではなく役割が異なり、なかでもanyioは「asyncioとTrioのどちらの上でも同じコードが動く」抽象レイヤーとして、FastAPIやStarletteといった主要フレームワークの内部でも使われている。ここでは各ライブラリの位置づけと基本的な使い方、比較の勘所、そして「どれを選ぶべきか」の判断基準までをコード付きで整理する。
まとめ:3つの関係と選び方の結論
asyncioはPython標準の非同期フレームワーク、Trioは構造化並行処理(structured concurrency)を最初に実装した外部ライブラリ、anyioはその考え方をasyncioとTrioの両バックエンドで使えるようにした抽象レイヤーだ。Python 3.11でasyncioにTaskGroupが入り、両者の差は縮まったが、キャンセル制御やAPIの一貫性ではまだanyio・Trioに分がある。ライブラリ配布や特定バックエンド非依存を求めるならanyio、単一アプリで最もクリーンな並行処理モデルが欲しいならTrio、標準構成で完結させたいならasyncioが基本線になる。以下で各ライブラリと違いを具体的に見ていく。
anyioとは:asyncioとTrioの両方で動く非同期抽象ライブラリ
anyioは、非同期アプリケーションやライブラリをasyncioまたはTrioのどちらのバックエンド上でも無修正で動かせるようにするネットワーク・並行処理ライブラリだ。TrioのAPIを参考に、タスクグループ・キャンセルスコープ・ソケット・ワーカースレッド・非同期ファイルI/Oといった機能を統一インターフェースで提供する。最新は2026年7月12日リリースの4.14.2で、Python 3.10以降を必要とする。かつて対応していたCurioバックエンドは3.0で廃止され、現在の対応バックエンドはasyncioとTrioの2つだ。
anyioを使う最大の利点は、書いたコードがバックエンドに縛られない点にある。ライブラリ作者はanyioで書いておけば、利用者がasyncioでもTrioでも同じライブラリを使える。既存のasyncio資産(Trioには無いasyncio専用ライブラリ)をそのまま活かしつつ、Trio由来の安全な並行処理モデルを取り込めるため、フルリファクタリングなしで段階的に導入できる。FastAPIの基盤であるStarletteが、スレッドプール実行やイベント処理でanyioを内部利用しているのはこの汎用性ゆえだ。
anyioの基本的な使い方(anyio.runとタスクグループ)
anyioのエントリポイントはanyio.run()で、実行するコルーチン関数を渡す。バックエンドは既定でasyncioだが、backend引数でTrioへ切り替えられる。複数タスクの並行実行にはanyio.create_task_group()(Trioの「ナーサリー」に相当)を使い、start_soon()でタスクを起こす。タスクグループを抜けるとき、内側のすべてのタスクの完了を待ち、いずれかが例外を投げれば残りをキャンセルして例外グループとして伝播する。
import anyio
async def worker(name):
await anyio.sleep(1)
print(name)
async def main():
async with anyio.create_task_group() as tg:
tg.start_soon(worker, "A")
tg.start_soon(worker, "B")
anyio.run(main) # asyncioバックエンド(既定)
anyio.run(main, backend="trio") # Trioバックエンドに切り替え
同期関数を非同期コード内で安全に呼ぶにはanyio.to_thread.run_sync()、逆にワーカースレッドからイベントループ側を呼ぶにはanyio.from_threadを使う。anyio.run(main)には関数オブジェクトを渡す点に注意する(後述のasyncioはasyncio.run(main())とコルーチンを渡す)。
Trioとは:構造化並行処理を最初に実装したライブラリ
Trioは、非同期タスクの生存範囲を明示的に区切る「構造化並行処理」をPythonで最初に体系化した外部ライブラリだ。中核はナーサリー(nursery)という仕組みで、タスクはナーサリーを開かない限り生成できない。ナーサリーはコンテキストマネージャで、その中で起こした全タスクが終了するまでブロックを抜けない。これにより「起動したまま放置されて漏れるタスク」が構造的に発生しない。
import trio
async def worker(name):
await trio.sleep(1)
print(name)
async def main():
async with trio.open_nursery() as nursery:
nursery.start_soon(worker, "A")
nursery.start_soon(worker, "B")
trio.run(main)
もう一つの柱がキャンセルスコープ(cancel scope)で、タイムアウトや明示的な中断を範囲として扱える。Trioは「どこで起こしたタスクでもスコープ単位で確実にキャンセルできる」設計になっており、この2つがanyioにもほぼそのまま取り込まれている。エントリポイントはtrio.run()で、こちらも関数オブジェクトを渡す。
anyio・Trio・asyncioの違い
| 観点 | asyncio | Trio | anyio |
|---|---|---|---|
| 提供 | 標準ライブラリ | 外部(pip) | 外部(pip) |
| 構造化並行処理 | TaskGroup(3.11〜) | nursery(当初から) | task group |
| キャンセル方式 | edge | level(cancel scope) | level(cancel scope) |
| バックエンド | asyncioのみ | Trioのみ | asyncio/Trio両対応 |
| 既存asyncio資産 | そのまま | 基本不可 | asyncio上なら流用可 |
構造化並行処理とasyncio.TaskGroupの位置づけ
Trioのナーサリーとanyioのタスクグループが実証したモデルは、Python 3.11(2022年10月)でasyncio.TaskGroupとasyncio.timeoutとして標準へ取り込まれた。標準ライブラリだけでも次のように同型の並行実行が書ける(anyio.run(main)とは異なりasyncio.runにはコルーチンmain()を渡す)。
import asyncio
async def worker(name):
await asyncio.sleep(1)
print(name)
async def main():
async with asyncio.TaskGroup() as tg:
tg.create_task(worker("A"))
tg.create_task(worker("B"))
asyncio.run(main())
これによりasyncioでも「ブロックを抜けるまで子タスクの完了を待つ」書き方ができるようになり、3.11以降のasyncioとanyio・Trioの差はかなり縮まった。ただしanyio公式が指摘するとおり、asyncio.TaskGroupはグループ内タスクの一覧取得やまとめてのキャンセルAPIを持たないため、複雑な中断制御を書くと自前の仕組みが必要になり誤りやすい。anyioのタスクグループは自身のキャンセルスコープを内蔵し、この操作を標準で備える。
キャンセル方式の違い(edge cancellationとlevel cancellation)
両者の設計差が最も出るのがキャンセルの扱いだ。asyncioはエッジ型(edge cancellation)で、CancelledErrorを原則1回だけ送る。このため例外を誤って握りつぶすとタスクが止まらず走り続けたり、再開待ちのタスクが結果を失ったりする落とし穴がある。対してTrioとanyioはレベル型(level cancellation)を採る。キャンセルされたタスクが制御をイベントループへ返すたびにCancelledErrorが再送されるため、一度の握りつぶしで中断が無効化されない。長時間のI/Oや外部呼び出しをタイムアウトで確実に打ち切りたい場面では、この違いが信頼性に直結する。
どのライブラリを選ぶべきか(判断基準)
3つは優劣ではなく適材適所で選ぶ。まず単発のアプリケーションで標準構成に収めたい、依存を増やしたくないならasyncioで十分だ。3.11以降はTaskGroupとtimeoutがあり、構造化並行処理の基本は標準だけで書ける。既存のasyncio専用ライブラリ(多くのDBドライバやHTTPクライアント)をそのまま使える点も大きい。
ライブラリを配布する、または特定バックエンドに縛られたくないならanyioを選ぶ。anyioで書けば利用者側がasyncioでもTrioでも動かせる。FastAPI/Starlette上のコードやASGIサーバー周辺のように、すでにanyioが土台になっている環境とも自然に噛み合う。
新規プロジェクトで最もクリーンな並行処理モデルを最優先するならTrioが向く。ナーサリーとキャンセルスコープが第一級で、設計思想が一貫している。ただしTrio専用エコシステムは小さいため、使いたいライブラリがasyncio前提の場合はtrio-asyncioのような橋渡しが要る。逆に言えば、チームがasyncio資産を多く抱え移行コストを避けたい場面ではTrio単独採用は避けるのが無難で、その場合はanyioでasyncioバックエンドを使いながらTrio的な書き味を得るのが現実的な折衷案になる。
よくある質問
anyioとasyncioはどちらが速いですか?
anyioはasyncioバックエンド上では薄い抽象レイヤーとして動くため、実行速度は素のasyncioとほぼ変わらない。選択理由は速度差ではなく、バックエンド非依存性とキャンセル制御の安全性にある。処理速度が最優先で標準構成に収めたいならasyncio、移植性を取るならanyioという住み分けになる。
Trioからasyncioへ移行することはできますか?
コードをanyioで書き直しておけば、anyio.run(main, backend="trio")とanyio.run(main)を切り替えるだけで両バックエンドを行き来できる。TrioのAPIを直接使っている場合は、open_nurseryをcreate_task_groupに置き換えるなどanyioのAPIへ寄せる作業が必要になる。
asyncio.TaskGroupがあればanyioは不要ですか?
単純な並行実行だけなら3.11以降のasyncio.TaskGroupで足りる。一方で、グループ内タスクの一括キャンセルやタスク一覧、レベル型キャンセルによる確実な中断が必要な場面ではanyioの方が扱いやすい。バックエンドを切り替えたい要件があるかどうかも判断材料になる。
anyioはどのPythonバージョンで使えますか?
最新の4.14.2はPython 3.10以降に対応する。バックエンドはasyncioまたはTrioで、以前対応していたCurioは3.0で廃止された。バージョンや対応範囲は変わりうるため、導入時は公式のversion history(anyio.readthedocs.io)で対応Pythonとバックエンドを確認する。