すべてのものを支配する1つのライブラリ:AnyIOによる非同期Python
Olivia Novak
Dev Intern · Leapcell

- 非同期プログラミング: プログラムが、I/Oバウンドな操作(Webサーバー、データベース操作、ネットワーク通信など)を扱う際に、ブロックせずに複数のタスクを並行して実行できるようにするプログラミングパラダイムです。
- イベントループ: 並行タスクをスケジュールして管理する非同期ランタイムの中心的なコンポーネントです。
- コルーチン: 実行を一時停止して後で再開できる、
async def
で定義された関数です。 asyncio
: Pythonに組み込まれた非同期I/Oフレームワークで、イベントループ、タスク、ストリーム、その他のプリミティブを提供します。これはfutureベースの並行処理を使用します。- Trio: 構造化された並行処理モデルで知られる代替非同期フレームワークであり、一般的な並行処理のバグを防ぎ、並行コードの推論を容易にします。タスク管理には nurseries を使用します。
- ** nursery (Trio)**: Trio の構成要素であり、新しい子タスクを生成でき、nursery 自体が終了する前にすべての子タスクが完了することを保証し、構造化された並行処理を強制します。
根本的な問題は、asyncio
とTrioの両方が優れた非同期プリミティブを提供しているにもかかわらず、APIが異なることです。asyncio
の asyncio.sleep()
用に書かれたコードは、Trio の trio.sleep()
と直接互換性がなく、asyncio.create_task()
はTrio の nursery ベースのタスク生成に直接相当するものはありません。このフレームワークのロックインは、ライブラリ作成者とアプリケーション開発者の両方にとって障害となります。
anyio
は互換性レイヤーとして機能します。これは、現在実行中のイベントループに応じて、asyncio
または Trio のいずれかの基盤上に実装された、一連の高レベルな非同期プリミティブを提供します。これは、コードを anyio
の API を使用して一度書けば、基盤となるバックエンドに自動的に適応することを意味します。
AnyIO の仕組み:
anyio
は次のことを行います。
- バックエンドの検出: 実行時に、
anyio
はasyncio
イベントループまたは Trio イベントループが実行中かどうかを検出しようとします。 - ユニバーサル API の提供: スリープ、並行タスクの実行、ロック、タスク間通信(キュー、イベント)などの一般的な非同期操作のために、統一された API を公開します。
- 呼び出しの翻訳:
anyio
のプリミティブ(例:await anyio.sleep(1)
)を呼び出すと、anyio
はそれを適切なバックエンド固有の呼び出し(例:await asyncio.sleep(1)
またはawait trio.sleep(1)
)に変換します。
実践的な応用:
1秒待機し、複数のタスクを並行して実行する簡単な例を考えてみましょう。
AnyIO なし(フレームワーク固有):
# asyncio 固有 import asyncio async def task_asyncio(name): print(f"Asyncio task {name}: Starting") await asyncio.sleep(0.1) print(f"Asyncio task {name}: Finishing") async def main_asyncio(): await asyncio.gather(task_asyncio("A"), task_asyncio("B")) # trio 固有 import trio async def task_trio(name): print(f"Trio task {name}: Starting") await trio.sleep(0.1) print(f"Trio task {name}: Finishing") async def main_trio(): async with trio.open_nursery() as nursery: nursery.start_soon(task_trio, "A") nursery.start_soon(task_trio, "B")
sleep
の違いやタスクの起動方法(asyncio.gather
対 trio.open_nursery
)に注意してください。
AnyIO あり(フレームワークに依存しない):
import anyio async def workers_agnostic(name): print(f"AnyIO task {name}: Starting") await anyio.sleep(0.1) print(f"AnyIO task {name}: Finishing") async def main_anyio(): async with anyio.create_task_group() as tg: tg.start_soon(workers_agnostic, "X") tg.start_soon(workers_agnostic, "Y") # asyncio で実行するには: # anyio.run(main_anyio, backend="asyncio") # trio で実行するには: # anyio.run(main_anyio, backend="trio")
ここでは、anyio.sleep()
と anyio.create_task_group()
がバックエンドの詳細を抽象化しています。anyio
の create_task_group
は Trio の nursery の直接の対応物として機能し、asyncio に互換性のある API を提供し、そのタスク管理機能を模倣しています。
高度な機能とシナリオ:
anyio
は、単純なタスク起動やスリープ以上のものを提供します。
- ネットワーキング:
anyio.connect_tcp()
,anyio.create_tcp_listener()
,anyio.connect_unix()
など、バックエンド間で機能するネットワーク接続の確立。 - 同期プリミティブ:
anyio.Lock
,anyio.Semaphore
,anyio.Event
,anyio.Queue
– いずれも基盤となるイベントループに関係なく確実に動作するように抽象化されています。 - ファイル I/O:
anyio.open_file()
を使用した非同期ファイル操作。 - 外部プロセス実行: 外部プロセスを非同期に起動および対話するための
await anyio.run_process()
。 - キャンセル処理:
anyio.CancelScope
は、タスクのキャンセルを管理するための統一された方法を提供し、asyncio
のキャンセルを Trio のより堅牢な構造化アプローチと整合させます。これは、anyio
がキャンセルを可能な限り両方のバックエンド間で一貫して動作することを積極的に保証することを意味し、信頼性にとって大きな利点です。
AnyIO を使用するタイミング:
- ライブラリ開発者:
asyncio
または Trio のどちらかで書かれたアプリケーションで使用される非同期ライブラリを構築している場合、anyio
は必須です。ライブラリのリーチを最大化し、メンテナンスのオーバーヘッドを削減します。 - 柔軟性を求めるアプリケーション開発者: パフォーマンス、機能セット、またはエコシステムの理由で、アプリケーションが
asyncio
と Trio を切り替える必要がある場合、anyio
はそのエスケープハッチを提供します。 - コードの簡素化: 単一のバックエンドのみを対象としている場合でも、
anyio
のしばしばよりクリーンで一貫性のある API は、特に構造化された並行処理のためのcreate_task_group
により、非同期コードの記述と推論を容易にすることができます。
結論
anyio
は、asyncio
と Trio の間の API の違いをうまく橋渡しする、Python 非同期エコシステムにおける重要なライブラリとして位置づけられています。統一された高レベルインターフェースを提供することで、開発者は真にポータブルな非同期コードを記述できるようになり、それによって柔軟性を高め、開発を簡素化し、よりまとまりのある堅牢な非同期環境を育成します。anyio
を使用すると、非同期ロジックを一度記述するだけで、プロジェクトのニーズに最適なバックエンドで自信を持って実行でき、コードの再利用性と将来性において大幅な改善を達成できます。