asyncioを使用した開発—Pythonドキュメント

提供:Dev Guides
< PythonPython/docs/3.9/library/asyncio-dev
移動先:案内検索

asyncioを使用した開発

非同期プログラミングは、従来の「シーケンシャル」プログラミングとは異なります。

このページでは、よくある間違いと落とし穴をリストし、それらを回避する方法を説明します。

デバッグモード

デフォルトでは、asyncioは本番モードで実行されます。 開発を容易にするために、asyncioにはデバッグモードがあります。

asyncioデバッグモードを有効にする方法はいくつかあります。

デバッグモードを有効にすることに加えて、次のことも考慮してください。

  • asyncio logger のログレベルをlogging.DEBUGに設定します。たとえば、アプリケーションの起動時に次のコードスニペットを実行できます。

    logging.basicConfig(level=logging.DEBUG)
  • warnings モジュールを構成して ResourceWarning 警告を表示します。 これを行う1つの方法は、 -W defaultコマンドラインオプションを使用することです。

デバッグモードが有効になっている場合:

  • asyncioは、待機されていないコルーチンをチェックし、それらをログに記録します。 これにより、「忘れられた待機」の落とし穴が軽減されます。
  • 多くの非スレッドセーフ非同期API( loop.call_soon()および loop.call_at()メソッドなど)は、間違ったスレッドから呼び出された場合に例外を発生させます。
  • I / O操作の実行に時間がかかりすぎる場合、I / Oセレクターの実行時間がログに記録されます。
  • 100ミリ秒以上かかるコールバックがログに記録されます。 loop.slow_callback_duration属性を使用して、「遅い」と見なされる最小実行時間を秒単位で設定できます。


並行性とマルチスレッド

イベントループはスレッド(通常はメインスレッド)で実行され、そのスレッド内のすべてのコールバックとタスクを実行します。 タスクがイベントループで実行されている間、他のタスクを同じスレッドで実行することはできません。 タスクがawait式を実行すると、実行中のタスクが一時停止され、イベントループが次のタスクを実行します。

別のOSスレッドからのコールバックをスケジュールするには、 loop.call_soon_threadsafe()メソッドを使用する必要があります。 例:

loop.call_soon_threadsafe(callback, *args)

ほとんどすべてのasyncioオブジェクトはスレッドセーフではありません。これは、タスクまたはコールバックの外部からそれらを操作するコードがない限り、通常は問題になりません。 低レベルの非同期APIを呼び出すためにこのようなコードが必要な場合は、 loop.call_soon_threadsafe()メソッドを使用する必要があります。例:

loop.call_soon_threadsafe(fut.cancel)

別のOSスレッドからコルーチンオブジェクトをスケジュールするには、 run_coroutine_threadsafe()関数を使用する必要があります。 結果にアクセスするために concurrent.futures.Future を返します。

async def coro_func():
     return await asyncio.sleep(1, 42)

# Later in another OS thread:

future = asyncio.run_coroutine_threadsafe(coro_func(), loop)
# Wait for the result:
result = future.result()

シグナルを処理し、サブプロセスを実行するには、イベントループをメインスレッドで実行する必要があります。

loop.run_in_executor()メソッドを concurrent.futures.ThreadPoolExecutor とともに使用すると、イベントループが実行されているOSスレッドをブロックせずに、別のOSスレッドでブロックコードを実行できます。

現在、別のプロセス(マルチプロセッシングで開始されたプロセスなど)から直接コルーチンまたはコールバックをスケジュールする方法はありません。 イベントループメソッドセクションには、イベントループをブロックせずに、パイプから読み取り、ファイル記述子を監視できるAPIが一覧表示されています。 さらに、asyncioの Subprocess APIは、プロセスを開始し、イベントループからプロセスと通信する方法を提供します。 最後に、前述のloop.run_in_executor()メソッドを concurrent.futures.ProcessPoolExecutor とともに使用して、別のプロセスでコードを実行することもできます。


ブロッキングコードの実行

ブロッキング(CPUバウンド)コードは直接呼び出さないでください。 たとえば、関数がCPUを集中的に使用する計算を1秒間実行すると、すべての同時非同期タスクとIO操作が1秒遅れます。

エグゼキュータを使用して、別のスレッドまたは別のプロセスでタスクを実行し、イベントループでOSスレッドがブロックされないようにすることができます。 詳細については、loop.run_in_executor()メソッドを参照してください。


ロギング

asyncioは logging モジュールを使用し、すべてのロギングは"asyncio"ロガーを介して実行されます。

デフォルトのログレベルはlogging.INFOで、簡単に調整できます。

logging.getLogger("asyncio").setLevel(logging.WARNING)

待望のコルーチンを検出する

コルーチン関数が呼び出されたが、待機されていない場合(例: await coro()の代わりにcoro())、またはコルーチンが asyncio.create_task()でスケジュールされていない場合、asyncioは RuntimeWarning を発行します。

import asyncio

async def test():
    print("never scheduled")

async def main():
    test()

asyncio.run(main())

出力:

test.py:7: RuntimeWarning: coroutine 'test' was never awaited
  test()

デバッグモードでの出力:

test.py:7: RuntimeWarning: coroutine 'test' was never awaited
Coroutine created at (most recent call last)
  File "../t.py", line 9, in <module>
    asyncio.run(main(), debug=True)

  < .. >

  File "../t.py", line 7, in main
    test()
  test()

通常の修正は、コルーチンを待つか、 asyncio.create_task()関数を呼び出すことです。

async def main():
    await test()

取得されなかった例外を検出する

Future.set_exception()が呼び出されたが、Futureオブジェクトが待機されない場合、例外がユーザーコードに伝播されることはありません。 この場合、Futureオブジェクトがガベージコレクションされると、asyncioはログメッセージを発行します。

未処理の例外の例:

import asyncio

async def bug():
    raise Exception("not consumed")

async def main():
    asyncio.create_task(bug())

asyncio.run(main())

出力:

Task exception was never retrieved
future: <Task finished coro=<bug() done, defined at test.py:3>
  exception=Exception('not consumed')>

Traceback (most recent call last):
  File "test.py", line 4, in bug
    raise Exception("not consumed")
Exception: not consumed

デバッグモードを有効にして、タスクが作成された場所のトレースバックを取得します。

asyncio.run(main(), debug=True)

デバッグモードでの出力:

Task exception was never retrieved
future: <Task finished coro=<bug() done, defined at test.py:3>
    exception=Exception('not consumed') created at asyncio/tasks.py:321>

source_traceback: Object created at (most recent call last):
  File "../t.py", line 9, in <module>
    asyncio.run(main(), debug=True)

< .. >

Traceback (most recent call last):
  File "../t.py", line 4, in bug
    raise Exception("not consumed")
Exception: not consumed