2025年9月: Python 3.14新機能: asyncioタスク可視化機能を使ってみよう¶
福田(@JunyaFff)です。2025年9月の「Python Monthly Topics」では、Python 3.14で追加されるasyncioタスク可視化機能である asyncio ps
コマンド asyncio pstree
コマンドと、asyncio.print_call_graph()
関数、 asyncio.capture_call_graph()
関数を紹介します。
はじめに¶
さまざまな機能強化が予定されているPython 3.14の中で、今回筆者が注目するのは、asyncioの新しい可視化ツールです。 asyncio ps
コマンド asyncio pstree
コマンドと、asyncio.print_call_graph()
関数や asyncio.capture_call_graph()
関数によって、実行中のasyncioタスクの状態を簡単に把握できるようになります。
状態とはつまり「どのようにタスクが呼ばれたか」「それによってどのタスクを待たせているか」を可視化できるようになります。

asyncio ps
コマンドの実行イメージ¶
また、既存のツールと異なる点として以下の特徴があります。
追加のデバッグコードをアプリケーションへ組み込む必要なし
別のプロセスから確認可能なため、オーバヘッドなし
本記事では機能追加の経緯、基本の使い方を中心に紹介します。本機能の動作確認は、2025年9月時点で公開されている最新のPython3.14.0rc3で、macOSにて行っております。DockerのOfficial Imageでも動作確認が可能ですので、興味のある方はぜひ試してみてください。
また、本機能に関する公式ドキュメントは以下になります。
リリースノート:https://docs.python.org/3.14/whatsnew/3.14.html#asyncio-introspection-capabilities 公式ドキュメント:https://docs.python.org/3.14/library/asyncio-graph.html
機能追加の経緯¶
従来のプロファイラ[1] はイベントループ内部のフレームばかりを表示し、タスクやコルーチンの呼び出し関係が追いづらい、という課題がありました。PyCon US 2025セッション「Zoom, Enhance: Asyncio’s New Introspection Powers」で取り上げられていたのが、本機能です。
これに対し、Meta社の「本番プロセスを止めずにasyncioタスクを追跡したい」という要望をきっかけに、コア開発スプリントで実装されたことが紹介されていました。
トークセッションの資料は以下をご参考ください。
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.print_call_graph()
関数、 asyncio.capture_call_graph()
関数の新規APIを呼び出す方法です。順に見ていきましょう。
コマンドラインツール asyncio ps/pstree
¶
CLIで呼び出す asyncio ps
コマンド pstree
コマンド は、すでに実行されているPythonのPID(Process ID)を引数に指定し実行します。
実行中のPythonプログラムの外から実行するため、既存コードの変更は不要でオーバーヘッドもほとんどありません。
まずは Python3.14 をインストールしてください。Python3.14でシンプルなPythonスクリプトを実行し、コマンドラインツールの動作を確認してみましょう。 実行するサンプルコードは以下のとおりです。
import asyncio
async def main():
await asyncio.sleep(500)
asyncio.run(main())
Python の PID を調べるには、OS(linux/mac)の ps
コマンドを利用します。 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・名前・コルーチンのスタック・await しているタスクの実行元を表形式で表示します。
python -m asyncio pstree PID
は同じ情報をawaitチェーンのツリーとして出力し、循環があれば検知します。
それぞれ出力される項目の説明は以下の通りです。
項目 |
説明 |
---|---|
tid |
Thread ID(スレッドID)。どのスレッドで実行されているタスクかを識別するための番号 |
task id |
asyncio内部で割り振られるタスク固有のID。個々のタスクを一意に識別するために用いられる。 |
task name |
タスクの名前。 |
coroutine stack |
現在のタスクがどのコルーチンを実行中かを「スタック」として表したもの。呼び出し元から現在のコルーチンまでの流れを確認できる。 |
awaiter chain |
そのタスクが |
awaiter name |
タスクが現在待っている別のタスクの名前。処理がどこで止まっているかを知る手がかりになる。 |
awaiter id |
|
シンプルな例なので、1行しか出力されていませんが、複数のタスクが存在する場合はそれぞれのタスクについて同様の情報が表示されます。 coroutine stack や awaiter chain の具体的な例は後述のサンプルコードで詳しく説明します。
プログラム内部で活用するデバッグ出力用API¶
CLIだけでなく、アプリケーション内部からタスクの状態を出力するAPIも追加されます。 asyncio.print_call_graph()
関数は現在(または明示的に指定した)タスクの他タスクとの関係とスタックを標準出力へ表示します。 depth
で上位フレームのスキップ、limit
でスタック深さの制限を指定できます。
asyncio.print_call_graph()
: https://docs.python.org/3.14/library/asyncio-graph.html#asyncio.print_call_graphasyncio.capture_call_graph()
: https://docs.python.org/3.14/library/asyncio-graph.html#asyncio.capture_call_graph
async def debug_task(task: asyncio.Task[Any]) -> None:
asyncio.print_call_graph(task, depth=1, limit=5)
より柔軟に扱いたいときは asyncio.capture_call_graph()
関数を使います。ログに残したり、GUIデバッガへ渡したりする用途に向いています。この関数では、 FutureCallGraph
オブジェクトを返し、以下の情報を個別に参照できます。
項目 |
説明 |
asyncio psでのどの出力に該当するか |
---|---|---|
future |
指定した task オブジェクトへの参照 |
task id や task name |
call_stack |
FrameCallGraphEntry オブジェクトのタプル |
coroutine stack |
awaited_by |
FutureCallGraph オブジェクトのタプル |
awaiter chain や awaiter name |
asyncio.capture_call_graph()
は以下のように利用可能です。
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.py
として保存し、以下のように実行します。
$ python3.14 restaurant.py
別のターミナルから先ほど実行したPythonプロセスのPIDを確認し、 asyncio ps
コマンド pstree
コマンドをそれぞれ実行します。「task name」「coroutine stack」「awaiter chain」に注目してください。
$ 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 name」が Task-1
と customer1.0
の2行です。 Task-1
は asyncio.run
で実行されている restaurant()
関数で、 customer1.0
は restaurant()
関数内で生成されたタスクです。
Task-1
の coroutine stack
を確認すると、sleep -> restaurant
となっております。 restaurant()
関数は、 asyncio.TaskGroup()
にて await asyncio.sleep(1000)
を実行しているため、このように出力されています。
続いて customer1.0
を確認してみましょう。「coroutine stack」を見ると、 customer1.0
タスクが sleep -> cooking -> chef -> waiter -> customer
という順番でawaitしていることがわかります。
「awaiter chain」と「awaiter name」をみてみましょう。awaiter とは 「あるタスクの完了を待っている(=awaitしている)タスク」のことです。
customer1.0
の「awaiter name」を見ると、 Task-1
とあります。Task-1
は asyncio.run
で実行されている restaurant()
関数です。つまり customer1.0
の awaiter は Task-1
です。「awaiter chain」を見ると、 sleep -> restaurant
となっていて、awaiter である Task-1
の状態を確認できます。
続いて、 pstree
コマンドを実行してみましょう。
さらに呼び出し元が明確になり、以下のようにツリー形式で表示されます。
pstree
コマンドでは、awaitの関係がツリー形式で表示されます。
上から順に Task-1
が restaurant()
あることがわかり、 customer1.0
である 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.14では _asyncio.Future
に _asyncio_awaited_by
属性が追加され、言語仕様として親タスクへの参照を保持するようになりました(FutureオブジェクトはTaskオブジェクトの元となるオブジェクトです。)
cpythonでの定義: cpython/Modules/_asynciomodule.c#L43 - GitHub
Taskオブジェクトに _asyncio_awaited_by
属性が含まれていることは、Python 3.14のREPLでも確認できます。
$ 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.14で追加されたasyncioのタスク可視化機能は、ゼロオーバーヘッドでタスク同士の関係を把握できるはじめての仕組みです。
python -m asyncio ps/pstree
による外部診断と、capture_call_graph
系APIによる内部ダンプを組み合わせれば、これまでブラックボックスだったawait待ちの連鎖を正確に追跡できます。既存の手法では困難だった本番環境でのデバッグやプロファイリングが現実的になりつつあります。正式リリースに向けて、まずは検証環境で新APIを試し、自身のプロジェクトにどう組み込むかを検討してみてください。
最後に私事ですが、 本機能についてPyCon JP 2025でも紹介しました。「タスクって今どうなってるの?3.14の新機能 asyncio ps と pstree でasyncioのデバッグを」 というトークです。気になる方はそちらもぜひご参考ください。
タスクって今どうなってるの?3.14の新機能 asyncio ps と pstree でasyncioのデバッグを | PyCon JP 2025