8.7. イベント

asyncio.Event は、このモジュールが提供する最もシンプルなシグナリングプリミティブです。あるコルーチンは、何かが起きたときにイベントを セット します。任意の数のほかのコルーチンは、続行する前にそのイベントがセットされるのを 待ち ます。ペイロードはありません。イベントはブール値であり、オンに切り替わると、何かがクリアするまでオンのままです。

8.7.1. 基本的な形

import asyncio

async def waiter(evt):
    print("waiting")
    await evt.wait()
    print("got signal")

async def main():
    evt = asyncio.Event()
    asyncio.create_task(waiter(evt))
    await asyncio.sleep(1)
    evt.set()
    await asyncio.sleep(0)

asyncio.run(main())

waiter タスクが実行され、await evt.wait() に到達してサスペンドされます。イベントはクリアされた状態で始まるため、wait 呼び出しはループに制御を返します。1 秒後、mainevt.set() を呼び出し、waiter は再開するようスケジュールされます。末尾の await asyncio.sleep(0) は単なる yield で、main が戻る前にループが waiter を実行する機会を得るためのものです。

8.7.2. 4 つのメソッド

  • set() -- イベントをセット状態に切り替え、現在 wait() でブロックされているすべてのコルーチンを再開するようスケジュールします。イベントはクリアされるまでセットのままです。

  • clear() -- イベントをクリア状態に戻します。その後 await wait() するコルーチンは、再びブロックされます。

  • wait() -- コルーチンです。イベントがすでにセットされていれば、ただちに返ります。そうでなければ、何かがセットするまでブロックします。

  • is_set() -- ブロックせずに現在の状態を返します。コルーチンがイベントを待たずに 確認 したいときに便利です。

8.7.3. 複数の waiter

複数のコルーチンが同じイベントを await できます。何かが set() を呼び出すと、それらは すべて 再開するようスケジュールされます。イベントは既定で 1 対多です。複数の waiter に分配するために、複数のイベントを用意する必要はありません。

8.7.4. イベントを再利用する

よくあるパターンは、各パスの後でイベントをクリアすることで、イベントを繰り返し使うこと(シグナルごとに 1 回実行されるコルーチン)です:

async def worker(go):
    while True:
        await go.wait()
        do_one_unit_of_work()
        go.clear()

プロデューサーは、処理があるたびにイベントをセットします。ワーカーは、シグナルを受け取ったらそれをクリアします。ワーカーが目を覚ます前にプロデューサーが再びイベントをセットする可能性がある場合、ワーカーはクリアする前にそのケースを処理(while go.is_set(): ...)したほうがよいかもしれません。

8.7.5. 安全なコンテキスト

Event は、割り込みハンドラの内側から set() を呼び出すのは 安全ではありません。waiter をスケジュールするために使う仕組みは、イベントループ自身のコンテキスト内で実行されていることを前提としていますが、割り込みはそうではありません。割り込みハンドラから asyncio タスクを起こすには、ThreadSafeFlag(まもなく取り上げます)が適切なプリミティブです。