asyncioを使用した開発
非同期プログラミングは、従来の「シーケンシャル」プログラミングとは異なります。
このページでは、よくある間違いと落とし穴をリストし、それらを回避する方法を説明します。
デバッグモード
デフォルトでは、asyncioは本番モードで実行されます。 開発を容易にするために、asyncioにはデバッグモードがあります。
asyncioデバッグモードを有効にする方法はいくつかあります。
- PYTHONASYNCIODEBUG 環境変数を
1
に設定します。 - Python開発モードを使用します。
debug=True
を asyncio.run()に渡します。- loop.set_debug()を呼び出します。
デバッグモードを有効にすることに加えて、次のことも考慮してください。
asyncio logger のログレベルを
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()メソッドを使用する必要があります。 例:
ほとんどすべてのasyncioオブジェクトはスレッドセーフではありません。これは、タスクまたはコールバックの外部からそれらを操作するコードがない限り、通常は問題になりません。 低レベルの非同期APIを呼び出すためにこのようなコードが必要な場合は、 loop.call_soon_threadsafe()メソッドを使用する必要があります。例:
別のOSスレッドからコルーチンオブジェクトをスケジュールするには、 run_coroutine_threadsafe()関数を使用する必要があります。 結果にアクセスするために concurrent.futures.Future を返します。
シグナルを処理し、サブプロセスを実行するには、イベントループをメインスレッドで実行する必要があります。
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
で、簡単に調整できます。
待望のコルーチンを検出する
コルーチン関数が呼び出されたが、待機されていない場合(例: await coro()
の代わりにcoro()
)、またはコルーチンが asyncio.create_task()でスケジュールされていない場合、asyncioは RuntimeWarning を発行します。
出力:
デバッグモードでの出力:
通常の修正は、コルーチンを待つか、 asyncio.create_task()関数を呼び出すことです。
取得されなかった例外を検出する
Future.set_exception()が呼び出されたが、Futureオブジェクトが待機されない場合、例外がユーザーコードに伝播されることはありません。 この場合、Futureオブジェクトがガベージコレクションされると、asyncioはログメッセージを発行します。
未処理の例外の例:
出力:
デバッグモードを有効にして、タスクが作成された場所のトレースバックを取得します。
デバッグモードでの出力: