福田asyncio psコマンド、asyncio pstreeコマンドと、asyncio.関数、asyncio.関数を紹介します。
はじめに
さまざまな機能強化が予定されているPython 3.asyncio psコマンド、asyncio pstreeコマンドと、asyncio.関数やasyncio.関数によって、実行中のasyncioタスクの状態を簡単に把握できるようになります。状態とはつまり
また、既存のツールと異なる点として、以下の特徴があります。
- 追加のデバッグコードをアプリケーションへ組み込む必要なし
- 別のプロセスから確認可能なため、オーバヘッドなし
本記事では機能追加の経緯、基本の使い方を中心に紹介します。本機能の動作確認は、2025年9月時点で公開されている最新のPython3.
また、本機能に関する公式ドキュメントは以下になります。
- リリースノート:Asyncio introspection capabilities -What's new in Python 3.
14 ―Python 3. 14. 0rc3 documentation - 公式ドキュメント:Call Graph Introspection ―Python 3.
14. 0rc3 documentation
機能追加の経緯
従来のプロファイラ[1]はイベントループ内部のフレームばかりを表示し、タスクやコルーチンの呼び出し関係が追いづらい、という課題がありました。PyCon US 2025セッション
これに対し、Meta社の
トークセッションの資料は以下をご参考ください。
- セッション概要:Zoom, Enhance: Asyncio's New Introspection Powers - PyCon US 2025
- 動画:Zoom, Enhance: Asyncio's New Introspection Powers - Pablo Galindo Salgado & Yury Selivanov - YouTube
asyncioでタスクの可視化をする方法
asyncioに追加された可視化機能には、大きく2種類の使い方があります。1つはコマンドラインツールとしてpython -m asyncio psコマンド、python -m asyncio pstreeコマンドを使う方法、もう1つはアプリケーション内部からasyncio.関数、asyncio.関数の新規APIを呼び出す方法です。順に見ていきましょう。
コマンドラインツール asyncio ps/pstree
CLIで呼び出すasyncio psコマンドとpstreeコマンドは、すでに実行されているPythonのPID
まずはPython3.
import asyncio
async def main():
await asyncio.sleep(500)
asyncio.run(main())
PythonのPIDを調べるには、OSpsコマンドを利用します。grepコマンドで絞り込むと便利です。
$ ps | grep Python ... 12345 ttys008 0:00.07 ... Python sample.py
コマンドラインツールは、以下のようにして実行します。
$ python3.14 -m asyncio ps 12345 ... 結果が出力される $ python3.14 -m asyncio pstree 12345 ... 結果が出力される
asyncio psコマンドの出力例を確認してみましょう。
$ python3.14 -m asyncio ps 12345 tid task id task name coroutine stack awaiter chain awaiter name awaiter id ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ 182457 0x102cf0050 Task-1 sleep -> main 0x0
python -m asyncio ps PIDは、現在のasyncioタスク一覧を取得し、タスクID・python -m asyncio pstree PIDは同じ情報をawaitチェーンのツリーとして出力し、循環があれば検知します。
それぞれ出力される項目の説明は以下の通りです。
| 項目 | 説明 |
|---|---|
| tid | Thread ID |
| task id | asyncio内部で割り振られるタスク固有のID。個々のタスクを一意に識別するために用いられる。 |
| task name | タスクの名前。asyncio.のように明示的に指定可能。未指定の場合は自動生成される。 |
| coroutine stack | 現在のタスクがどのコルーチンを実行中かを |
| awaiter chain | そのタスクがawaitしている対象。どの処理がどの処理を待っているかを示す。 |
| awaiter name | タスクが現在待っている別のタスクの名前。処理がどこで止まっているかを知る手がかりになる。 |
| awaiter id | awaitしている対象に割り振られた一意のID。複数の対象を区別するために使われる。 |
シンプルな例なので1行しか出力されていませんが、複数のタスクが存在する場合はそれぞれのタスクについて同様の情報が表示されます。coroutine stackやawaiter chainの具体的な例は、後述のサンプルコードで詳しく説明します。
プログラム内部で活用するデバッグ出力用API
CLIだけでなく、アプリケーション内部からタスクの状態を出力するAPIも追加されます。asyncio.関数は現在depthで上位フレームのスキップ、limitでスタック深さの制限を指定できます。
asyncio.-Call Graph Introspection ―Python 3.print_ call_ graph() 14. 0rc3 documentation asyncio.-Call Graph Introspection ―Python 3.capture_ call_ graph() 14. 0rc3 documentation
async def debug_task(task: asyncio.Task[Any]) -> None:
asyncio.print_call_graph(task, depth=1, limit=5)
より柔軟に扱いたいときはasyncio.関数を使います。ログに残したり、GUIデバッガへ渡したりする用途に向いています。この関数ではFutureCallGraphオブジェクトを返し、以下の情報を個別に参照できます。
| 項目 | 説明 | asyncio psでのどの出力に該当するか |
|---|---|---|
| future | 指定したtaskオブジェクトへの参照 | task idやtask name |
| call_ |
FrameCallGraphEntryオブジェクトのタプル | coroutine stack |
| awaited_ |
FutureCallGraphオブジェクトのタプル | awaiter chainやawaiter name |
asyncio.は以下のように利用可能です。
graph = asyncio.capture_call_graph(task: asyncio.Task[Any], depth=1, limit=5)
for frame in graph.call_stack:
print(frame)
limit=Noneで全フレーム、limit=0でawait状態のタスクオブジェクトだけを取得できるので、必要な情報量に合わせて使い分けます。これらのAPIは既存コードに組み込んでも負荷が小さいため、問題が再現した瞬間にダンプするといった運用が可能になります。
具体的なコードを可視化してみよう
以下のサンプルコードを用意しました。asyncio psコマンド、pstreeコマンドを試してみましょう。どこのタスクがどのタスクを待っているか、またそのタスクがどのように呼び出されたかを一目で把握できます。
サンプルコードでは、restaurant()からcustomer()を実行しそこからそれぞれwaiter()、chef()、cooking()の順番に内部でawaitしています。
イメージとしては以下のようなつながりのあるコードです。
import asyncio
async def customer():
await waiter()
async def waiter():
await chef()
async def chef():
await cooking()
async def cooking():
await asyncio.sleep(500)
async def restaurant():
async with asyncio.TaskGroup() as tg:
tg.create_task(customer(), name="customer1.0") # タスク名を customer1.0 に設定
await asyncio.sleep(1000)
asyncio.run(restaurant())
上記コードをrestaurant.として保存し、以下のように実行します。
$ python3.14 restaurant.py
別のターミナルから先ほど実行したPythonプロセスのPIDを確認し、asyncio psコマンドとpstreeコマンドをそれぞれ実行します。
$ ps | grep Python ... 12345 ttys008 0:00.07 ... Python restaurant.py $ python3.14 -m asyncio ps 12345 tid task id task name coroutine stack awaiter chain awaiter name awaiter id ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ 2897749 0x104e78230 Task-1 sleep -> restaurant 0x0 2897749 0x104e78410 customer1.0 sleep -> cooking -> chef -> waiter -> customer sleep -> restaurant Task-1 0x104e78230
2行出力されていることがわかります。Task-1とcustomer1.の2行です。Task-1はasyncio.で実行されているrestaurant()関数で、customer1.はrestaurant()関数内で生成されたタスクです。
Task-1のcoroutine stackを確認すると、sleep -> restaurantとなっております。restaurant()関数は、asyncio.にてawait asyncio.を実行しているため、このように出力されています。
続いてcustomer1.を確認してみましょう。customer1.タスクがsleep -> cooking -> chef -> waiter -> customerという順番でawaitしていることがわかります。
「awaiter chain」
customer1.のTask-1とあります。Task-1はasyncio.で実行されているrestaurant()関数です。つまりcustomer1.のawaiterはTask-1です。sleep -> restaurantとなっていて、awaiterであるTask-1の状態を確認できます。
続いて、pstreeコマンドを実行してみましょう。
さらに呼び出し元が明確になり、以下のようにツリー形式で表示されます。pstreeコマンドでは、awaitの関係がツリー形式で表示されます。上から順にTask-1がrestaurant()あることがわかり、customer1.であるcustomer()関数がwaiter()関数、waiter()関数がchef()関数、chef()関数がcooking()関数を呼んでいて、最終的にsleepしていることが明示されます。
$ python3.14 -m asyncio ps 12345
└── (T) Task-1
└── restaurant /home/user/sample/restaurant.py:36
└── sleep /usr/lib/python3.14/asyncio/tasks.py:702
└── (T) customer1.0
└── customer /home/user/sample/restaurant.py:5
└── waiter /home/user/sample/restaurant.py:9
└── chef /home/user/sample/restaurant.py:13
└── cooking /home/user/sample/restaurant.py:17
└── sleep /usr/lib/python3.14/asyncio/tasks.py:702
これらの可視化によって、意図的に実行されているか、想定外のところでawaitしていないか、などを簡単に把握できるようになります。特に複雑な非同期処理を扱う場合に有用です。
どのように実現されているかを紹介
この機能がどのように実現されているか少し紹介します。Python 3._asyncio.に_asyncio_属性が追加され、言語仕様として親タスクへの参照を保持するようになりました
- cpythonでの定義:cpython/
Modules/ _asynciomodule. c#L43 - GitHub
Taskオブジェクトに_asyncio_属性が含まれていることは、Python 3.
$ python3.14 -m asyncio asyncio REPL 3.14.0rc3 (v3.14.0rc3:1c5b28405a7, Sep 18 2025, 10:24:24) [Clang 16.0.0 (clang-1600.0.26.6)] on darwin Use "await" directly instead of "asyncio.run()". Type "help", "copyright", "credits" or "license" for more information. >>> async def foo(): ... await asyncio.sleep(1) ... >>> task = asyncio.create_task(foo()) >>> dir(task) [... '_asyncio_awaited_by' ...]
まとめ
Python 3.
python -m asyncio ps/による外部診断と、capture_系APIによる内部ダンプを組み合わせれば、これまでブラックボックスだったawait待ちの連鎖を正確に追跡できます。既存の手法では困難だった本番環境でのデバッグやプロファイリングが現実的になりつつあります。正式リリースに向けて、まずは検証環境で新APIを試し、自身のプロジェクトにどう組み込むかを検討してみてください。
最後に私事ですが、本機能についてPyCon JP 2025でも紹介しました。
