コルーチンとタスク¶
この節では、コルーチンと Task を利用する高レベルの asyncio の API の概略を解説します。
コルーチン¶
ソースコード: Lib/asyncio/coroutines.py
async/await 構文で宣言された コルーチン は、 asyncio を使ったアプリケーションを書くのに推奨される方法です。例えば、次のコードスニペットは "hello" を出力し、そこから 1 秒待って "world" を出力します:
>>> import asyncio
>>> async def main():
... print('hello')
... await asyncio.sleep(1)
... print('world')
>>> asyncio.run(main())
hello
world
単にコルーチンを呼び出しただけでは、コルーチンの実行スケジュールは予約されていないことに注意してください:
>>> main()
<coroutine object main at 0x1053bb7c8>
実際にコルーチンを実行するために、asyncio は以下のメカニズムを提供しています:
最上位のエントリーポイントである "main()" 関数を実行する
asyncio.run()関数 (上の例を参照してください。)コルーチンを await すること。次のコード片は 1 秒間待機した後に "hello" と出力し、 更に 2 秒間待機してから "world" と出力します:
import asyncio import time async def say_after(delay, what): await asyncio.sleep(delay) print(what) async def main(): print(f"started at {time.strftime('%X')}") await say_after(1, 'hello') await say_after(2, 'world') print(f"finished at {time.strftime('%X')}") asyncio.run(main())
予想される出力:
started at 17:13:52 hello world finished at 17:13:55
asyncio の
Tasksとしてコルーチンを並行して走らせるasyncio.create_task()関数。上のコード例を編集して、ふたつの
say_afterコルーチンを 並行して 走らせてみましょう:async def main(): task1 = asyncio.create_task( say_after(1, 'hello')) task2 = asyncio.create_task( say_after(2, 'world')) print(f"started at {time.strftime('%X')}") # 両方のタスクが完了するまで待つ (約 2 秒かかる)。 await task1 await task2 print(f"finished at {time.strftime('%X')}")
予想される出力が、スニペットの実行が前回よりも 1 秒早いことを示していることに注意してください:
started at 17:14:32 hello world finished at 17:14:34
asyncio.TaskGroupクラスは、create_task()に代わるより新しい手段です。 この API を使うと、直前の例は次のようになります。async def main(): async with asyncio.TaskGroup() as tg: task1 = tg.create_task( say_after(1, 'hello')) task2 = tg.create_task( say_after(2, 'world')) print(f"started at {time.strftime('%X')}") # コンテキストマネージャーを抜けるときに暗黙に await する。 print(f"finished at {time.strftime('%X')}")
実行にかかる時間と出力は、直前の例と同じになるはずです。
Added in version 3.11:
asyncio.TaskGroup.
Awaitable¶
あるオブジェクトを await 式の中で使うことができる場合、そのオブジェクトを awaitable オブジェクトと言います。多くの asyncio API は awaitable を受け取るように設計されています。
awaitable オブジェクトには主に3つの種類があります: コルーチン, Task, そして Future です
コルーチン
Python のコルーチンは awaitable であり、他のコルーチンから待機されることができます:
import asyncio
async def nested():
return 42
async def main():
# "nested()" を呼び出すだけでは何も起こらない。
# コルーチンオブジェクトは作成されるが await されないので、
# まったく実行されない。
nested() # "RuntimeWarning" が発生する。
# 今度は await してみる:
print(await nested()) # "42" と表示する。
asyncio.run(main())
重要
このドキュメントにおいて「コルーチン」という用語は以下2つの密接に関連した概念に対して使用できます:
コルーチン関数:
async def関数;コルーチンオブジェクト: コルーチン関数 を呼び出すと返ってくるオブジェクト.
Task
Task は、コルーチンを 並行に スケジュールするのに使います。
asyncio.create_task() のような関数で、コルーチンが Task にラップされているとき、自動的にコルーチンは即時実行されるようにスケジュールされます:
import asyncio
async def nested():
return 42
async def main():
# nested() がまもなく "main()" と並行して実行されるようにする。
task = asyncio.create_task(nested())
# "task" で "nested()" をキャンセルしたり、
# 単に await して完了を待ったりできる:
await task
asyncio.run(main())
Future
Future は、非同期処理の 最終結果 を表現する特別な 低レベルの awaitable オブジェクトです。
Future オブジェクトが 待機 (await) されている とは、Future がどこか他の場所で解決されるまでコルーチンが待機するということです。
asyncioのFutureオブジェクトを使うと、async/awaitとコールバック形式のコードを併用できます。
通常、アプリケーション水準のコードで Future オブジェクトを作る 必要はありません 。
Future オブジェクトはライブラリや asyncio のAPIで外部に提供されることもあり、await (待機)されることができます:
async def main():
await function_that_returns_a_future_object()
# これも有効:
await asyncio.gather(
function_that_returns_a_future_object(),
some_python_coroutine()
)
Future オブジェクトを返す低レベル関数の良い例は loop.run_in_executor() です。
タスクの作成¶
ソースコード: Lib/asyncio/tasks.py
- asyncio.create_task(coro, *, name=None, context=None, eager_start=None, **kwargs)¶
coro coroutine を
Taskでラップし、その実行をスケジュールします。 Task オブジェクトを返します。関数のシグネチャ全体は
Taskのコンストラクター (またはファクトリー) とほぼ同じです。 この関数に渡したキーワード引数はすべて、そのコンストラクターやファクトリーに渡されます。省略可能なキーワード引数 context によって、coro を実行するためのカスタムの
contextvars.Contextを指定できます。context が省略された場合、現在のコンテキストのコピーが作成されます。省略可能なキーワード専用引数 eager_start では、
create_task()の呼び出し中にタスクの実行を即座に開始するか、後で実行するようにスケジュールするかを指定できます。 eager_start を渡さない場合はloop.set_task_factory()で設定したモードを使います。その Task オブジェクトは
get_running_loop()から返されたループの中で実行されます。現在のスレッドに実行中のループが無い場合は、RuntimeErrorが送出されます。注釈
asyncio.TaskGroup.create_task()は、構造化された並行処理を使う新しい代替手段です。 強い安全性の保証のもとで、関連するタスクのグループを待機できます。重要
タスクが実行中に消えないように、この関数の結果の参照を保存してください。イベントループは弱い参照のみを保持します。ほかに参照元のないタスクは、完了していなくてもガーベジコレクションされる可能性があります。信頼性のある "fire-and-forget" バックグラウンドタスクが必要な場合、コレクションを使ってください。
background_tasks = set() for i in range(10): task = asyncio.create_task(some_coro(param=i)) # タスクを集合に追加する。これにより強参照が作成される。 background_tasks.add(task) # 完了したタスクへの参照を保持し続けないように、 # 各タスクが完了したら集合から自身への参照を削除するようにする: task.add_done_callback(background_tasks.discard)
この方法ではタスクを await しないため、タスクが失敗しても例外は取得されません。 タスクがガベージコレクションされると、asyncio は "Task exception was never retrieved" というメッセージをログに記録します。 これを避けるには
asyncio.TaskGroupを使ってください。 各タスクへの強参照を保持してタスクを await し、例外を伝播させます。async with asyncio.TaskGroup() as tg: for i in range(10): tg.create_task(some_coro(param=i))
Added in version 3.7.
バージョン 3.8 で変更: name パラメータを追加しました。
バージョン 3.11 で変更: context パラメータを追加しました。
バージョン 3.14 で変更: すべての kwargs を渡すようにすることで、eager_start パラメーターが追加されました。
タスクのキャンセル¶
タスクは簡単に、そして安全にキャンセルできます。タスクがキャンセルされた場合、asyncio.CancelledError が次の機会に送出されます。
後片付けを確実に行うため、コルーチンでは try/finally ブロックを使うことを推奨します。
asyncio.CancelledError を明示的に捕捉した場合、通常は後片付けが終わったら伝播させてください。
asyncio.CancelledError は BaseException の直接のサブクラスなので、ほとんどのコードでは特別に意識する必要はありません。
asyncio.TaskGroup や asyncio.timeout() など、構造化された並行処理を実現する asyncio の構成要素は内部でキャンセルを使って実装されています。
コルーチンが asyncio.CancelledError を握りつぶすと、正しく動作しない場合があります。
同様に、ユーザーのコードから uncancel を呼び出すことも通常は避けてください。
ただし、本当に asyncio.CancelledError を抑制したい場合は、キャンセル状態を完全に解除するために uncancel() も呼び出す必要があります。
タスクグループ¶
タスクグループは、タスクを作成する API と、グループ内のすべてのタスクの終了を便利かつ確実に待つ手段を組み合わせたものです。
- class asyncio.TaskGroup¶
タスクのグループを保持する 非同期コンテキストマネージャー です。
create_task()でタスクをグループに追加できます。 コンテキストマネージャーを抜けるときに、すべてのタスクを await します。Added in version 3.11.
- create_task(coro, *, name=None, context=None, eager_start=None, **kwargs)¶
このタスクグループにタスクを作成します。 シグネチャは
asyncio.create_task()と同じです。 タスクグループが非アクティブ (まだ開始していない、すでに終了した、終了処理中など) の場合は、渡されたcoroを閉じてRuntimeErrorを送出します。バージョン 3.13 で変更: タスクグループがアクティブでない場合、渡されたコルーチンを閉じるようになりました。
バージョン 3.14 で変更: すべての kwargs を
loop.create_task()に渡すようになりました。
- cancel()¶
タスクグループをキャンセルします。 例外を発生させずにタスクグループの処理を早期に終了します。 グループの目的を達成した場合や、グループの処理が不要になった場合に役立ちます。
グループ内でまだ完了していないタスクと、グループの親 (本体) に対して
cancel()を呼び出します。 タスクグループのコンテキストマネージャーは、asyncio.CancelledErrorを送出 せずに 終了します。タスクグループに入る前に
cancel()を呼び出すと、グループに入った時点でキャンセルされます。 これは、あるコードから別のコードへ未使用のasyncio.TaskGroupインスタンスを渡し、グループ内で実行される処理をキャンセルできるようにするパターンで役立ちます。cancel()は冪等で、タスクグループがすでに終了した後にも呼び出せます。cancel()の使用方法の例:条件やイベントに応じてタスクグループの本体から呼び出す
create_task()で子タスクにタスクグループのインスタンスを渡し、子タスクが条件に応じてグループ全体をキャンセルできるようにするタスクグループを開始する 前に、タスクグループのインスタンスまたは束縛された
cancel()メソッドを他のタスクに渡し、そのタスクからキャンセルできるようにする
Added in version 3.15.
以下はプログラム例です:
async def main():
async with asyncio.TaskGroup() as tg:
task1 = tg.create_task(some_coro(...))
task2 = tg.create_task(another_coro(...))
print(f"Both tasks have completed now: {task1.result()}, {task2.result()}")
async with 文はグループ内のすべてのタスクが終了するまで待ちます。
待機中も新しいタスクをグループに追加できます (例えば、いずれかのコルーチンに tg を渡し、そのコルーチンで tg.create_task() を呼び出します)。
条件に応じて tg.cancel() でタスクグループ全体の終了を要求することもできます。
最後のタスクが終了して async with ブロックを抜けると、グループに新しいタスクを追加することはできません。
グループ内のいずれかのタスクが asyncio.CancelledError 以外の例外で初めて失敗した時点で、グループ内の残りのタスクをキャンセルします。
それ以降はグループにタスクを追加できません。
この時点で async with 文の本体がまだ実行中 (つまり __aexit__() がまだ呼び出されていない) なら、async with 文を直接含むタスクもキャンセルされます。
これによって生じる asyncio.CancelledError は await を中断しますが、それを含む async with 文の外には伝播しません。
すべてのタスクが終了した時点で、asyncio.CancelledError 以外の例外で失敗したタスクがあれば、それらの例外を ExceptionGroup または BaseExceptionGroup にまとめて送出します (どちらを使うかは、それぞれのドキュメントを参照してください)。
基底例外のうち、2 つは特別に扱います。
いずれかのタスクが KeyboardInterrupt または SystemExit で失敗した場合も、タスクグループは残りのタスクをキャンセルして終了を待ちます。
ただし、その後は ExceptionGroup や BaseExceptionGroup ではなく、最初の KeyboardInterrupt または SystemExit を再送出します。
async with 文の本体が例外によって終了した場合 (つまり例外が設定された状態で __aexit__() が呼び出された場合) は、タスクが失敗した場合と同じように扱います。
残りのタスクをキャンセルして終了を待ち、キャンセル以外の例外を例外グループにまとめて送出します。
__aexit__() に渡された例外も、asyncio.CancelledError でなければ例外グループに含めます。
KeyboardInterrupt と SystemExit には、前の段落と同じ特別な扱いをします。
async with の本体に限り、さらに特別な扱いがあります。
本体が GeneratorExit を送出し、他のタスクが報告対象となる例外を送出しなかった場合は、その GeneratorExit を再送出します。
タスクグループは、__aexit__() を「起こす」ための内部的なキャンセルと、タスクグループが実行されているタスクに対する外部からのキャンセル要求を区別します。
特に、タスクグループが構文上入れ子になっていて、両方のグループの子タスクで同時に例外が発生した場合は、まず内側のグループが例外を処理します。
その後、外側のグループが別のキャンセルを受け取り、自身の例外を処理します。
タスクグループが外部からキャンセルされ、かつ ExceptionGroup を送出する必要もある場合は、親タスクの cancel() メソッドを呼び出します。
これにより、次の await で asyncio.CancelledError が送出され、キャンセルが失われないようにします。
タスクグループは asyncio.Task.cancelling() が返すキャンセル回数を保持します。
バージョン 3.13 で変更: 内部と外部からのキャンセルが同時に発生した場合の処理が改善され、キャンセル回数が正しく保持されるようになりました。
バージョン 3.15 で変更: GeneratorExit の特別な扱いが追加されました。
スリープ¶
- async asyncio.sleep(delay, result=None)¶
delay 秒だけ停止します。
result が提供されている場合は、コルーチン完了時にそれが呼び出し元に返されます。
sleep()は常に現在の Task を一時中断し、他の Task が実行されるのを許可します。delayを 0 に設定すると、最適化された処理経路で他のタスクを実行できるようにします。 長時間実行する関数でこれを使うと、関数の呼び出し中ずっとイベントループをブロックしてしまうのを避けられます。現在の時刻を5秒間、毎秒表示するコルーチンの例:
import asyncio import datetime as dt async def display_date(): loop = asyncio.get_running_loop() end_time = loop.time() + 5.0 while True: print(dt.datetime.now()) if (loop.time() + 1.0) >= end_time: break await asyncio.sleep(1) asyncio.run(display_date())
バージョン 3.10 で変更: loop パラメータが削除されました。
バージョン 3.13 で変更: delay が
nanの場合にValueErrorを送出するようになりました。
タスクの並行実行¶
- awaitable asyncio.gather(*aws, return_exceptions=False)¶
aws シーケンスにある awaitable オブジェクト を 並行 実行します。
aws にある awaitable がコルーチンである場合、自動的に Task としてスケジュールされます。
全ての awaitable が正常終了した場合、その結果は返り値を集めたリストになります。 返り値の順序は、 aws での awaitable の順序に相当します。
return_exceptions が
Falseである場合(デフォルト)、gather()で await しているタスクに対して、最初の例外が直接伝えられます。aws に並んでいる他の awaitable は、キャンセルされずに 引き続いて実行されます。return_exceptions が
Trueだった場合、例外は成功した結果と同じように取り扱われ、結果リストに集められます。gather()が キャンセル された場合、起動された全ての (未完了の) awaitable も キャンセル されます。aws シーケンスにある Task あるいは Future が キャンセル された場合、
CancelledErrorを送出したかのうように扱われます。つまり、この場合gather()呼び出しはキャンセル されません。 これは、起動された 1 つの Task あるいは Future のキャンセルが、他の Task あるいは Future のキャンセルを引き起こすのを避けるためです。注釈
タスクを作成して並行に実行し、完了を待つための新しい代替手段として
asyncio.TaskGroupがあります。 サブタスクを入れ子にスケジュールする場合、TaskGroup は gather よりも強い安全性を保証します。 タスク (またはタスクがスケジュールしたサブタスク) が例外を送出すると、TaskGroup はスケジュール済みの残りのタスクをキャンセルしますが、gather はキャンセルしません。以下はプログラム例です:
import asyncio async def factorial(name, number): f = 1 for i in range(2, number + 1): print(f"Task {name}: Compute factorial({number}), currently i={i}...") await asyncio.sleep(1) f *= i print(f"Task {name}: factorial({number}) = {f}") return f async def main(): # 3 つの呼び出しを並行に実行するようスケジュールする: L = await asyncio.gather( factorial("A", 2), factorial("B", 3), factorial("C", 4), ) print(L) asyncio.run(main()) # 期待される出力: # # Task A: Compute factorial(2), currently i=2... # Task B: Compute factorial(3), currently i=2... # Task C: Compute factorial(4), currently i=2... # Task A: factorial(2) = 2 # Task B: Compute factorial(3), currently i=3... # Task C: Compute factorial(4), currently i=3... # Task B: factorial(3) = 6 # Task C: Compute factorial(4), currently i=4... # Task C: factorial(4) = 24 # [2, 6, 24]
注釈
return_exceptions が偽の場合、完了状態になった後で
gather()をキャンセルしても、渡された awaitable はキャンセルされません。 例えば、gatherは例外を呼び出し元に伝播した時点で完了状態になることがあります。 そのため、いずれかの awaitable が送出した例外をgatherから捕捉した後でgather.cancel()を呼び出しても、他の awaitable はキャンセルされません。バージョン 3.7 で変更: gather 自身がキャンセルされた場合は、 return_exceptions の値に関わらずキャンセルが伝搬されます。
バージョン 3.10 で変更: loop パラメータが削除されました。
バージョン 3.10 で非推奨: 位置引数が渡されていない場合、または位置引数のすべてが Future のようなオブジェクトというわけではなく、かつ実行中のイベントループがない場合に、非推奨の警告を出すようになりました。
即時実行するタスクファクトリー¶
- asyncio.eager_task_factory(loop, coro, *, name=None, context=None)¶
タスクの実行を即座に開始するタスクファクトリーです。
このファクトリーを
loop.set_task_factory(asyncio.eager_task_factory)で使うと、Taskの構築中にコルーチンの実行を同期的に開始します。 タスクがイベントループにスケジュールされるのは、処理がブロックした場合だけです。 同期的に完了するコルーチンではループへのスケジュールに伴うオーバーヘッドを避けられるため、性能が向上する場合があります。これが役立つ典型例は、キャッシュやメモ化を使って可能な限り実際の I/O を避けるコルーチンです。
注釈
コルーチンを即座に実行すると、実行の意味が変わります。 コルーチンが値を返すか例外を送出すると、タスクはイベントループにスケジュールされません。 コルーチンの実行がブロックすると、タスクはイベントループにスケジュールされます。 この変更により、既存のアプリケーションの動作が変わる可能性があります。 例えば、タスクの実行順序は変わる可能性が高いでしょう。
Added in version 3.12.
- asyncio.create_eager_task_factory(custom_task_constructor)¶
eager_task_factory()と同様に即時実行するタスクファクトリーを作成します。 新しいタスクの作成には、デフォルトのTaskの代わりに、指定された custom_task_constructor を使います。custom_task_constructor は
Task.__init__と同じシグネチャを持つ 呼び出し可能オブジェクト でなければなりません。 このオブジェクトはasyncio.Taskと互換性のあるオブジェクトを返す必要があります。この関数は、
loop.set_task_factory(factory)でイベントループのタスクファクトリーとして使うための 呼び出し可能オブジェクト を返します。Added in version 3.12.
キャンセルからの保護¶
- awaitable asyncio.shield(aw)¶
キャンセルから awaitable オブジェクト を保護します。aw がコルーチンだった場合、自動的に Task としてスケジュールされます。
文:
task = asyncio.create_task(something()) res = await shield(task)
は、以下と同じです
res = await something()
ただし、これを含むコルーチンがキャンセルされても、
something()を実行している Task はキャンセルされない という違いがあります。something()側から見ると、キャンセルは発生していません。 呼び出し元はキャンセルされるため、"await" 式はCancelledErrorを送出します。注意:
something()が他の理由 (例えば、原因が自分自身) でキャンセルされた場合はshield()でも保護できません。完全にキャンセルを無視したい場合 (推奨はしません) は、
shield()関数は次のように try/except 節と組み合わせることになるでしょう:task = asyncio.create_task(something()) try: res = await shield(task) except CancelledError: res = None
重要
タスクが実行途中で消えないように、この関数に渡すタスクへの参照を保持してください。 イベントループはタスクへの弱参照しか保持しません。 他から参照されていないタスクは、完了前でもいつでもガベージコレクションされる可能性があります。
バージョン 3.10 で変更: loop パラメータが削除されました。
バージョン 3.10 で非推奨: aw が Future のようなオブジェクトでなく、実行中のイベントループもない場合に、非推奨の警告を出すようになりました。
タイムアウト¶
- asyncio.timeout(delay)¶
待機に費やす時間を制限できる 非同期コンテキストマネージャー を返します。
delay には
None、または待機する秒数を浮動小数点数か整数で指定できます。 delay がNoneの場合、時間制限は適用されません。 コンテキストマネージャーの作成時点で待機時間が分からない場合に便利です。どちらの場合も、作成後に
Timeout.reschedule()でコンテキストマネージャーの期限を変更できます。以下はプログラム例です:
async def main(): async with asyncio.timeout(10): await long_running_task()
long_running_taskの完了に 10 秒を超えてかかると、コンテキストマネージャーは現在のタスクをキャンセルします。 その結果生じるasyncio.CancelledErrorを内部で処理し、捕捉して処理できるTimeoutErrorに変換します。注釈
asyncio.CancelledErrorをTimeoutErrorに変換するのはasyncio.timeout()のコンテキストマネージャーです。 そのため、TimeoutErrorを捕捉できるのはコンテキストマネージャーの 外側 だけです。TimeoutErrorを捕捉する例です。async def main(): try: async with asyncio.timeout(10): await long_running_task() except TimeoutError: print("The long operation timed out, but we've handled it.") print("This statement will run regardless.")
asyncio.timeout()が生成するコンテキストマネージャーは、期限を変更したり、状態を調べたりできます。- class asyncio.Timeout(when)¶
期限を超えたコルーチンをキャンセルするための 非同期コンテキストマネージャー です。
Timeoutを直接インスタンス化するよりも、asyncio.timeout()またはasyncio.timeout_at()を使うことを推奨します。whenには、イベントループの時計で計測した、タイムアウトすべき絶対時刻を指定してください。whenがNoneの場合、タイムアウトは発生しません。when < loop.time()の場合、イベントループの次の反復でタイムアウトが発生します。
以下はプログラム例です:
async def main(): try: # 開始時には期限が分からないので、``None`` を渡す。 async with asyncio.timeout(None) as cm: # 期限が分かったので設定する。 new_deadline = get_running_loop().time() + 10 cm.reschedule(new_deadline) await long_running_task() except TimeoutError: pass if cm.expired(): print("Looks like we haven't finished on time.")
タイムアウトのコンテキストマネージャーは安全に入れ子にできます。
Added in version 3.11.
- asyncio.timeout_at(when)¶
asyncio.timeout()と似ていますが、when には待機を終了する絶対時刻、またはNoneを指定します。以下はプログラム例です:
async def main(): loop = get_running_loop() deadline = loop.time() + 20 try: async with asyncio.timeout_at(deadline): await long_running_task() except TimeoutError: print("The long operation timed out, but we've handled it.") print("This statement will run regardless.")
Added in version 3.11.
- async asyncio.wait_for(aw, timeout)¶
aw awaitable が、完了するかタイムアウトになるのを待ちます。
timeout には
Noneもしくは待つ秒数の浮動小数点数か整数を指定できます。 timeout がNoneの場合、 Future が完了するまで待ちます。タイムアウトが発生すると aw をキャンセルし、
TimeoutErrorを送出します。aw がキャンセルされないようにするには
shield()でラップしてください。この関数は future が実際にキャンセルされるまで待つため、待ち時間の合計は timeout を超えることがあります。キャンセル中に例外が発生した場合は、その例外は伝達されます。
待機が中止された場合 aw も中止されます。
以下はプログラム例です:
async def eternity(): # 1 時間スリープする await asyncio.sleep(3600) print('yay!') async def main(): # 最大 1 秒待つ try: await asyncio.wait_for(eternity(), timeout=1.0) except TimeoutError: print('timeout!') asyncio.run(main()) # 期待される出力: # # timeout!
バージョン 3.7 で変更: タイムアウトにより aw をキャンセルする場合、
wait_forは aw のキャンセルが完了するまで待つようになりました。 以前はTimeoutErrorを即座に送出していました。バージョン 3.10 で変更: loop パラメータが削除されました。
バージョン 3.11 で変更:
asyncio.TimeoutErrorの代わりにTimeoutErrorを送出するようになりました。バージョン 3.12 で変更:
asyncio.timeout()を使った実装になり、timeout が正の場合に aw として渡されたコルーチンをTaskでラップしなくなりました。
待機プリミティブ¶
- async asyncio.wait(aws, *, timeout=None, return_when=ALL_COMPLETED)¶
イテラブル aws に含まれる
FutureとTaskのインスタンスを並行に実行し、return_when で指定した条件が満たされるまでブロックします。イテラブル aws は空であってはなりません。
Task/Future からなる 2 つの集合
(done, pending)を返します。使い方:
done, pending = await asyncio.wait(aws)
timeout (浮動小数点数または整数) が指定されていたら、処理を返すのを待つ最大秒数を制御するのに使われます。
この関数は
TimeoutErrorを送出しません。 タイムアウト時点で完了していない Future や Task は、単に 2 番目の集合に入れて返されます。return_when でこの関数がいつ結果を返すか指定します。指定できる値は以下の 定数のどれか一つです:
定数
説明
- asyncio.FIRST_COMPLETED¶
いずれかのフューチャが終了したかキャンセルされたときに返します。
- asyncio.FIRST_EXCEPTION¶
いずれかの Future が例外を送出して終了したときに返ります。 どの Future も例外を送出しなければ
ALL_COMPLETEDと同じです。- asyncio.ALL_COMPLETED¶
すべてのフューチャが終了したかキャンセルされたときに返します。
wait_for()と異なり、wait()はタイムアウトが起きたときに Future をキャンセルしません。wait()がキャンセルされても、aws 内の Future はキャンセルされずに実行を続けます。バージョン 3.10 で変更: loop パラメータが削除されました。
バージョン 3.11 で変更:
wait()にコルーチンオブジェクトを直接渡すことは禁止されました。バージョン 3.12 で変更: タスクを yield するジェネレーターをサポートするようになりました。
- asyncio.as_completed(aws, *, timeout=None)¶
イテラブル aws に含まれる awaitable オブジェクト を並行に実行します。 返されたオブジェクトを反復すると、awaitable の完了に応じて結果を取得できます。
as_completed()が返すオブジェクトは asynchronous iterator としても通常の iterator としても反復できます。 非同期反復の場合、もともと渡された awaitable が Task または Future なら、そのオブジェクトを yield します。 これにより、スケジュール済みのタスクと結果を簡単に対応付けられます。 例えば次のように使います。ipv4_connect = create_task(open_connection("127.0.0.1", 80)) ipv6_connect = create_task(open_connection("::1", 80)) tasks = [ipv4_connect, ipv6_connect] async for earliest_connect in as_completed(tasks): # earliest_connect は完了している。await するか # earliest_connect.result() を呼び出すと結果を取得できる。 reader, writer = await earliest_connect if earliest_connect is ipv6_connect: print("IPv6 connection established.") else: print("IPv4 connection established.")
非同期反復では、渡された awaitable が Task や Future でない場合、暗黙に作成されたタスクを yield します。
通常のイテレーターとして使う場合、反復のたびに新しいコルーチンを yield します。 このコルーチンは、次に完了する awaitable の結果を返すか、その例外を送出します。 この使い方は Python 3.13 より前のバージョンと互換性があります。
ipv4_connect = create_task(open_connection("127.0.0.1", 80)) ipv6_connect = create_task(open_connection("::1", 80)) tasks = [ipv4_connect, ipv6_connect] for next_connect in as_completed(tasks): # next_connect は元のタスクオブジェクトではない。 # await すると、次に完了する awaitable の結果を取得するか、 # その例外を送出する。 reader, writer = await next_connect
すべての awaitable が完了する前にタイムアウトすると、
TimeoutErrorを送出します。 非同期反復ではasync forループが、通常の反復では yield されたコルーチンが、この例外を送出します。as_completed()は、渡された awaitable を実行しているタスクをキャンセルしません。 タイムアウトした場合や反復がキャンセルされた場合も、残りのタスクは実行を続けます。バージョン 3.10 で変更: loop パラメータが削除されました。
バージョン 3.10 で非推奨: イテラブル aws に含まれる awaitable オブジェクトのすべてが Future のようなオブジェクトというわけではなく、かつ実行中のイベントループがない場合に、非推奨の警告を出すようになりました。
バージョン 3.12 で変更: タスクを yield するジェネレーターをサポートするようになりました。
バージョン 3.13 で変更: 結果を asynchronous iterator としても通常の iterator としても使えるようになりました (以前は通常のイテレーターとしてのみ使えました)。
スレッドでの実行¶
- async asyncio.to_thread(func, /, *args, **kwargs)¶
別のスレッドで非同期的に関数 func を実行します。
この関数に渡された *args と **kwargs は関数 func に直接渡されます。また、イベントループスレッドのコンテキスト変数に関数を実行するスレッドからアクセスできるように、現在の
contextvars.Contextも伝播されます。関数 func の最終結果を待ち受けできるコルーチンを返します。
このコルーチン関数は主に、メインスレッドで実行するとイベントループをブロックしてしまう I/O バウンドの関数やメソッドを実行するためのものです。 例えば次のように使います。
def blocking_io(): print(f"start blocking_io at {time.strftime('%X')}") # time.sleep() は、ファイル操作などのブロックする # I/O バウンドの処理に置き換えられる。 time.sleep(1) print(f"blocking_io complete at {time.strftime('%X')}") async def main(): print(f"started main at {time.strftime('%X')}") await asyncio.gather( asyncio.to_thread(blocking_io), asyncio.sleep(1)) print(f"finished main at {time.strftime('%X')}") asyncio.run(main()) # 期待される出力: # # started main at 19:50:53 # start blocking_io at 19:50:53 # blocking_io complete at 19:50:54 # finished main at 19:50:54
コルーチン内で
blocking_io()を直接呼び出すと、実行中ずっとイベントループをブロックするため、実行時間が 1 秒長くなります。 代わりにasyncio.to_thread()を使えば、イベントループをブロックせずに別のスレッドで実行できます。注釈
Due to the GIL,
asyncio.to_thread()can typically only be used to make IO-bound functions non-blocking. However, for extension modules that release the GIL or alternative Python implementations that don't have one,asyncio.to_thread()can also be used for CPU-bound functions.Added in version 3.9.
他のスレッドからのスケジュール¶
- asyncio.run_coroutine_threadsafe(coro, loop)¶
与えられたイベントループにコルーチンを送ります。 この処理は、スレッドセーフです。
他の OS スレッドから結果を待つための
concurrent.futures.Futureを返します。この関数は、イベントループが動作しているスレッドとは異なる OS スレッドから呼び出すためのものです。 例えば次のように使います:
def in_thread(loop: asyncio.AbstractEventLoop) -> None: # ブロックする I/O を実行する pathlib.Path("example.txt").write_text("hello world", encoding="utf8") # コルーチンを作成する coro = asyncio.sleep(1, result=3) # 指定されたループにコルーチンを送る future = asyncio.run_coroutine_threadsafe(coro, loop) # 省略可能な timeout 引数を指定して結果を待つ assert future.result(timeout=2) == 3 async def amain() -> None: # 実行中のループを取得する loop = asyncio.get_running_loop() # スレッドで処理を実行する await asyncio.to_thread(in_thread, loop)
逆に、別のスレッドで実行するイベントループへ処理を送ることもできます。 例えば次のようにします。
@contextlib.contextmanager def loop_in_thread() -> Generator[asyncio.AbstractEventLoop]: loop_fut = concurrent.futures.Future[asyncio.AbstractEventLoop]() stop_event = asyncio.Event() async def main() -> None: loop_fut.set_result(asyncio.get_running_loop()) await stop_event.wait() with concurrent.futures.ThreadPoolExecutor(1) as tpe: complete_fut = tpe.submit(asyncio.run, main()) for fut in concurrent.futures.as_completed((loop_fut, complete_fut)): if fut is loop_fut: loop = loop_fut.result() try: yield loop finally: loop.call_soon_threadsafe(stop_event.set) else: fut.result() # 別のスレッドにループを作成する with loop_in_thread() as loop: # コルーチンを作成する coro = asyncio.sleep(1, result=3) # 指定されたループにコルーチンを送る future = asyncio.run_coroutine_threadsafe(coro, loop) # 省略可能な timeout 引数を指定して結果を待つ assert future.result(timeout=2) == 3
コルーチンから例外が送出された場合、返された Future に通知されます。 これはイベントループの Task をキャンセルするのにも使えます:
try: result = future.result(timeout) except TimeoutError: print('The coroutine took too long, cancelling the task...') future.cancel() except Exception as exc: print(f'The coroutine raised an exception: {exc!r}') else: print(f'The coroutine returned: {result!r}')
このドキュメントの 並行処理とマルチスレッド処理 節を参照してください。
他の asyncio 関数とは異なり、この関数は明示的に渡される loop 引数を必要とします。
Added in version 3.5.1.
イントロスペクション¶
- asyncio.current_task(loop=None)¶
現在実行中の
Taskインスタンスを返します。実行中の Task が無い場合はNoneを返します。loop が
Noneの場合、get_running_loop()が現在のループを取得するのに使われます。Added in version 3.7.
- asyncio.all_tasks(loop=None)¶
ループで実行された
Taskオブジェクトでまだ完了していないものの集合を返します。loop が
Noneの場合、get_running_loop()は現在のループを取得するのに使われます。Added in version 3.7.
- asyncio.iscoroutine(obj)¶
obj がコルーチンオブジェクトなら
Trueを返します。Added in version 3.4.
Task オブジェクト¶
- class asyncio.Task(coro, *, loop=None, name=None, context=None, eager_start=False)¶
Python コルーチン を実行する
Future 類オブジェクトです。 スレッドセーフではありません。Task はイベントループのコルーチンを実行するのに使われます。 Future でコルーチンが待機している場合、 Task は自身のコルーチンの実行を一時停止させ、 Future の完了を待ちます。 Future が 完了 したら、 Task が内包しているコルーチンの実行を再開します。
イベントループは協調スケジューリングを使用します。つまり、イベントループは同時に 1 つの Task のみ実行します。 Task が Future の完了を待っているときは、イベントループは他の Task やコールバックを動作させるか、 IO 処理を実行します。
Task を作成するには高レベルの
asyncio.create_task()関数、あるいは低レベルのloop.create_task()関数やensure_future()関数を使用してください。 Task を直接インスタンス化することは推奨されません。実行中の Task をキャンセルするには
cancel()メソッドを使ってください。 このメソッドを呼び出すと、Task がラップしているコルーチンにCancelledErrorを送出します。 キャンセル時にコルーチンが Future のようなオブジェクトを await している場合、そのオブジェクトもキャンセルされます。cancelled()を使うと Task がキャンセルされたかどうかを確認できます。 Task が内包するコルーチンがCancelledError例外を抑制せず、実際にキャンセルされた場合、このメソッドはTrueを返します。asyncio.Taskは、Future.set_result()とFuture.set_exception()を除いて、Futureの API をすべて継承しています。省略可能なキーワード専用引数 context では、coro を実行するための独自の
contextvars.Contextを指定できます。 context を指定しない場合、Task は現在のコンテキストをコピーし、後でそのコピーの中でコルーチンを実行します。省略可能なキーワード専用引数 eager_start では、
asyncio.Taskの作成時に実行を即座に開始するよう指定できます。Trueに設定され、イベントループが実行中であれば、タスクはコルーチンの実行を即座に開始し、コルーチンが初めてブロックするまで実行します。 コルーチンがブロックせずに値を返すか例外を送出すると、タスクはその場で完了し、イベントループへのスケジュールを省略します。Task は、ラップするコルーチンの戻り値の型を型パラメーターとする ジェネリック型 です。
バージョン 3.7 で変更:
contextvarsモジュールのサポートを追加。バージョン 3.8 で変更: name パラメータを追加しました。
バージョン 3.10 で非推奨: loop が指定されておらず、実行中のイベントループもない場合に、非推奨の警告を出すようになりました。
バージョン 3.11 で変更: context パラメータを追加しました。
バージョン 3.12 で変更: eager_start パラメーターが追加されました。
- done()¶
Task が 完了 しているなら
Trueを返します。Task がラップしているコルーチンが値を返すか、例外を送出するか、または Task がキャンセルされたとき、 Task は 完了 します。
- result()¶
Task の結果を返します。
Task が 完了 している場合、ラップしているコルーチンの結果が返されます (コルーチンが例外を送出された場合、その例外が例外が再送出されます)
Task が キャンセル されている場合、このメソッドは
CancelledError例外を送出します。Task の結果がまだ利用できない場合、このメソッドは
InvalidStateErrorを送出します。
- exception()¶
Task の例外を返します。
ラップされたコルーチンが例外を送出した場合、その例外が返されます。ラップされたコルーチンが正常終了した場合、このメソッドは
Noneを返します。Task が キャンセル されている場合、このメソッドは
CancelledError例外を送出します。Task がまだ 完了 していない場合、このメソッドは
InvalidStateError例外を送出します。
- add_done_callback(callback, *, context=None)¶
Task が 完了 したときに実行されるコールバックを追加します。
このメソッドは低水準のコールバックベースのコードでのみ使うべきです。
詳細については
Future.add_done_callback()のドキュメントを参照してください。
- remove_done_callback(callback)¶
コールバックリストから callback を削除します。
このメソッドは低水準のコールバックベースのコードでのみ使うべきです。
詳細については
Future.remove_done_callback()のドキュメントを参照してください。
- get_stack(*, limit=None)¶
このタスクのスタックフレームのリストを返します。
コルーチンが完了していない場合、これはサスペンドされた時点でのスタックを返します。コルーチンが正常に処理を完了したか、キャンセルされていた場合は空のリストを返します。コルーチンが例外で終了した場合はトレースバックフレームのリストを返します。
フレームは常に古いものから新しい物へ並んでいます。
サスペンドされているコルーチンの場合スタックフレームが 1 個だけ返されます。
省略可能な limit 引数は返すフレーム数の上限を指定します。 デフォルトでは取得可能なすべてのフレームを返します。 返されるリストの順序は、スタックとトレースバックのどちらを返すかによって異なります。 スタックの場合は最も新しいフレーム群、トレースバックの場合は最も古いフレーム群が返されます (これは traceback モジュールの動作と一致します)。
- print_stack(*, limit=None, file=None)¶
このタスクのスタックまたはトレースバックを出力します。
このメソッドは
get_stack()によって取得されるフレームに対し、 traceback モジュールと同じような出力を生成します。引数 limit は
get_stack()にそのまま渡されます。file 引数は出力先の I/O ストリームです。 デフォルトでは
sys.stdoutに出力します。
- get_coro()¶
Taskがラップしているコルーチンオブジェクトを返します。注釈
即時実行ですでに完了した Task では
Noneを返します。 即時実行するタスクファクトリー を参照してください。Added in version 3.8.
バージョン 3.12 で変更: 即時実行するタスクが追加されたため、結果が
Noneになる場合があります。
- get_context()¶
タスクに関連付けられた
contextvars.Contextオブジェクトを返します。Added in version 3.12.
- get_name()¶
Task の名前を返します。
Task に対して明示的に名前が設定されていない場合, デフォルトの asyncio Task 実装はタスクをインスタンス化する際にデフォルトの名前を生成します。
Added in version 3.8.
- set_name(value)¶
Task に名前を設定します。
引数 value は文字列に変換可能なオブジェクトであれば何でもかまいません。
デフォルトの Task 実装では、タスクオブジェクトの
repr()の出力で名前を確認できます。Added in version 3.8.
- cancel(msg=None)¶
このタスクに、自身のキャンセルを要求します。
Task がすでに 完了 または キャンセル されている場合は
Falseを、それ以外の場合はTrueを返します。このメソッドは、イベントループの次のサイクルで、ラップしているコルーチンに
CancelledErrorを送出するように手配します。コルーチンは、
try...except CancelledError...finallyブロックで後片付けをしたり、例外を抑制してキャンセル要求を拒否したりできます。 このため、Future.cancel()と異なり、Task.cancel()は Task がキャンセルされることを保証しません。 ただし、キャンセルを完全に抑制するのは一般的ではなく、強く非推奨とします。 それでもキャンセルを抑制する場合、コルーチンは例外を捕捉するだけでなくTask.uncancel()も呼び出す必要があります。キャンセルされる Task が Future のようなオブジェクトを await している場合、そのオブジェクトもキャンセルされます。 このキャンセルは、await されているオブジェクトの連鎖全体に伝播します。
バージョン 3.9 で変更: msg パラメーターが追加されました。
バージョン 3.11 で変更:
msgパラメーターが、キャンセルされたタスクからそのタスクを await する側へ伝播するようになりました。次の例は、コルーチンがキャンセル要求を捕捉する方法を示しています。
async def cancel_me(): print('cancel_me(): before sleep') try: # 1 時間待つ await asyncio.sleep(3600) except asyncio.CancelledError: print('cancel_me(): cancel sleep') raise finally: print('cancel_me(): after sleep') async def main(): # "cancel_me" タスクを作成する task = asyncio.create_task(cancel_me()) # 1 秒待つ await asyncio.sleep(1) task.cancel() try: await task except asyncio.CancelledError: print("main(): cancel_me is cancelled now") asyncio.run(main()) # 期待される出力: # # cancel_me(): before sleep # cancel_me(): cancel sleep # cancel_me(): after sleep # main(): cancel_me is cancelled now
- cancelled()¶
Task が キャンセルされた 場合に
Trueを返します。cancel()メソッドによりキャンセルがリクエストされ、かつ Task がラップしているコルーチンが内部で送出されたCancelledError例外を伝達したとき、 Task は実際に キャンセル されます。
- uncancel()¶
この Task に対するキャンセル要求の回数を減らします。
残っているキャンセル要求の回数を返します。
キャンセルされたタスクの実行が完了した後は、
uncancel()を呼び出しても効果はありません。Added in version 3.11.
このメソッドは asyncio の内部で使うもので、ユーザーのコードからの使用は想定していません。 特に、Task のキャンセル状態を正常に解除できると、キャンセルの影響をそれぞれの構造化されたブロック内に限定でき、タスクグループ や
asyncio.timeout()などの構造化された並行処理を継続できます。 例えば次のようになります。async def make_request_with_timeout(): try: async with asyncio.timeout(1): # タイムアウトの影響を受ける構造化されたブロック: await make_request() await make_another_request() except TimeoutError: log("There was a timeout") # 外側のコードはタイムアウトの影響を受けない: await unrelated_code()
make_request()とmake_another_request()を含むブロックはタイムアウトによってキャンセルされる可能性がありますが、その場合もunrelated_code()は実行を続ける必要があります。 これを実現するのがuncancel()です。TaskGroupコンテキストマネージャーも同様にuncancel()を使います。何らかの理由でユーザーのコードが
CancelledErrorを捕捉してキャンセルを抑制する場合、キャンセル状態を解除するためにこのメソッドを呼び出す必要があります。このメソッドによってキャンセル回数がゼロになると、以前の
cancel()の呼び出しがタスクへのCancelledErrorの送出を手配していたかどうかを確認します。 まだ送出されていなければ、内部の_must_cancelフラグをリセットして、その手配を取り消します。
バージョン 3.13 で変更: キャンセル回数がゼロになったら、保留中のキャンセル要求を取り消すようになりました。
- cancelling()¶
この Task に対する保留中のキャンセル要求の回数、つまり
cancel()の呼び出し回数からuncancel()の呼び出し回数を引いた値を返します。この回数がゼロより大きくても、Task がまだ実行中なら
cancelled()はFalseを返します。uncancel()を呼び出してこの回数を減らし、キャンセル要求がゼロになれば、最終的にタスクがキャンセルされないこともあるためです。このメソッドは asyncio の内部で使うもので、ユーザーのコードからの使用は想定していません。 詳しくは
uncancel()を参照してください。Added in version 3.11.