18.5.9。 asyncioで開発する
非同期プログラミングは、従来の「シーケンシャル」プログラミングとは異なります。 このページでは、一般的なトラップを一覧表示し、それらを回避する方法について説明します。
18.5.9.1。 asyncioのデバッグモード
asyncio の実装は、パフォーマンスのために作成されています。 非同期コードの開発を容易にするために、デバッグモードを有効にすることをお勧めします。
アプリケーションのすべてのデバッグチェックを有効にするには:
- 環境変数 PYTHONASYNCIODEBUG を
1
に設定するか、 AbstractEventLoop.set_debug()を呼び出すことにより、非同期デバッグモードをグローバルに有効にします。 - 非同期ロガーのログレベルを
logging.DEBUG
に設定します。 たとえば、起動時にlogging.basicConfig(level=logging.DEBUG)
を呼び出します。 - 警告モジュールを構成して、 ResourceWarning 警告を表示します。 たとえば、Pythonの
-Wdefault
コマンドラインオプションを使用してそれらを表示します。
デバッグチェックの例:
- ログコルーチンは定義されていますが、「生成」されることはありません
- call_soon()および call_at()メソッドは、間違ったスレッドから呼び出された場合に例外を発生させます。
- セレクターの実行時間をログに記録する
- 実行に100ミリ秒以上かかるコールバックをログに記録します。
AbstractEventLoop.slow_callback_duration
属性は、「遅い」コールバックの最小期間(秒単位)です。 - ResourceWarning 警告は、トランスポートとイベントループが明示的に閉じられていない場合に発行されます。
18.5.9.2。 キャンセル
タスクのキャンセルは、従来のプログラミングでは一般的ではありません。 非同期プログラミングでは、それは一般的なことであるだけでなく、それを処理するためのコードを準備する必要があります。
先物とタスクは、 Future.cancel()メソッドを使用して明示的にキャンセルできます。 wait_for()
関数は、タイムアウトが発生すると、待機中のタスクをキャンセルします。 タスクを間接的にキャンセルできるケースは他にもたくさんあります。
フューチャーがキャンセルされた場合は、 Future の set_result()または set_exception()メソッドを呼び出さないでください。例外で失敗します。 たとえば、次のように記述します。
AbstractEventLoop.call_soon()を使用して、futureの set_result()または set_exception()メソッドへの呼び出しを直接スケジュールしないでください。そのメソッドが呼び出される前にキャンセルされました。
将来を待つ場合は、無駄な操作を避けるために、将来がキャンセルされたかどうかを早めに確認する必要があります。 例:
shield()
機能を使用して、キャンセルを無視することもできます。
18.5.9.3。 並行性とマルチスレッド
イベントループはスレッドで実行され、同じスレッドですべてのコールバックとタスクを実行します。 タスクがイベントループで実行されている間、他のタスクは同じスレッドで実行されていません。 ただし、タスクがyield from
を使用すると、タスクは一時停止され、イベントループが次のタスクを実行します。
別のスレッドからのコールバックをスケジュールするには、 AbstractEventLoop.call_soon_threadsafe()メソッドを使用する必要があります。 例:
ほとんどのasyncioオブジェクトはスレッドセーフではありません。 イベントループの外側のオブジェクトにアクセスする場合にのみ心配する必要があります。 たとえば、futureをキャンセルするには、その Future.cancel()メソッドを直接呼び出さないでください。ただし、次のようにします。
シグナルを処理し、サブプロセスを実行するには、イベントループをメインスレッドで実行する必要があります。
別のスレッドからコルーチンオブジェクトをスケジュールするには、 run_coroutine_threadsafe()関数を使用する必要があります。 結果にアクセスするために concurrent.futures.Future を返します。
AbstractEventLoop.run_in_executor()
メソッドをスレッドプールエグゼキュータとともに使用して、イベントループのスレッドをブロックしないように、別のスレッドでコールバックを実行できます。
も参照してください
同期プリミティブセクションでは、タスクを同期する方法について説明します。
サブプロセスとスレッドセクションには、異なるスレッドからサブプロセスを実行するための非同期の制限がリストされています。
18.5.9.4。 ブロッキング機能を正しく処理する
ブロッキング関数は直接呼び出さないでください。 たとえば、関数が1秒間ブロックすると、他のタスクが1秒間遅延し、反応性に重要な影響を与える可能性があります。
ネットワークとサブプロセスの場合、 asyncio モジュールはプロトコルのような高レベルのAPIを提供します。
エグゼキュータを使用して、別のスレッドまたは別のプロセスでタスクを実行し、イベントループのスレッドをブロックしないようにすることができます。 AbstractEventLoop.run_in_executor()
メソッドを参照してください。
18.5.9.5。 ロギング
asyncio モジュールは、ロガー'asyncio'
の logging モジュールを使用して情報をログに記録します。
asyncio モジュールのデフォルトのログレベルはlogging.INFO
です。 asyncio からこのような冗長性を望まない場合は、ログレベルを変更できます。 たとえば、レベルをlogging.WARNING
に変更するには、次のようにします。
logging.getLogger('asyncio').setLevel(logging.WARNING)
18.5.9.6。 スケジュールされていないコルーチンオブジェクトを検出する
コルーチン関数が呼び出され、その結果が ensure_future()または AbstractEventLoop.create_task()メソッドに渡されない場合、コルーチンオブジェクトの実行はスケジュールされません。おそらくバグです。 asyncio のデバッグモードを有効にして、警告をログに記録して検出します。
バグの例:
デバッグモードでの出力:
修正は、コルーチンオブジェクトを使用して sure_future()関数または AbstractEventLoop.create_task()メソッドを呼び出すことです。
18.5.9.7。 消費されたことのない例外を検出する
Pythonは通常、未処理の例外に対して sys.excepthook()を呼び出します。 Future.set_exception()が呼び出されたが、例外が消費されなかった場合、 sys.excepthook()は呼び出されません。 代わりに、ガベージコレクターによってfutureが削除されると、ログが発行されます。例外が発生したトレースバックがあります。
未処理の例外の例:
出力:
asyncio のデバッグモードを有効にして、タスクが作成された場所のトレースバックを取得します。 デバッグモードでの出力:
この問題を修正するには、さまざまなオプションがあります。 最初のオプションは、コルーチンを別のコルーチンにチェーンし、従来のtry / exceptを使用することです。
別のオプションは、 AbstractEventLoop.run_until_complete()関数を使用することです。
18.5.9.8。 コルーチンを正しくチェーンする
コルーチン関数が他のコルーチン関数およびタスクを呼び出す場合、それらはyield from
で明示的にチェーンする必要があります。 それ以外の場合、実行は順次であることが保証されません。
asyncio.sleep()
を使用して低速操作をシミュレートするさまざまなバグの例:
期待される出力:
(1) create file
(2) write into file
(3) close file
Pending tasks at exit: set()
実際の出力:
(3) close file
(2) write into file
Pending tasks at exit: {<Task pending create() at test.py:7 wait_for=<Future pending cb=[Task._wakeup()]>>}
Task was destroyed but it is pending!
task: <Task pending create() done at test.py:5 wait_for=<Future pending cb=[Task._wakeup()]>>
create()
が終了する前にループが停止し、close()
がwrite()
の前に呼び出されたのに対し、コルーチン関数はcreate()
、 [の順序で呼び出されました。 X162X]、close()
。
例を修正するには、タスクにyield from
のマークを付ける必要があります。
またはasyncio.ensure_future()
なし:
18.5.9.9。 保留中のタスクが破棄されました
保留中のタスクが破棄された場合、ラップされたコルーチンの実行は完了しませんでした。 おそらくバグであるため、警告がログに記録されます。
ログの例:
Task was destroyed but it is pending!
task: <Task pending coro=<kill_me() done, defined at test.py:5> wait_for=<Future pending cb=[Task._wakeup()]>>
asyncio のデバッグモードを有効にして、タスクが作成された場所のトレースバックを取得します。 デバッグモードでのログの例:
Task was destroyed but it is pending!
source_traceback: Object created at (most recent call last):
File "test.py", line 15, in <module>
task = asyncio.ensure_future(coro, loop=loop)
task: <Task pending coro=<kill_me() done, defined at test.py:5> wait_for=<Future pending cb=[Task._wakeup()] created at test.py:7> created at test.py:15>
18.5.9.10。 トランスポートとイベントループを閉じる
トランスポートが不要になったら、そのclose()
メソッドを呼び出してリソースを解放します。 イベントループも明示的に閉じる必要があります。
トランスポートまたはイベントループが明示的に閉じられていない場合、 ResourceWarning 警告がデストラクタで発行されます。 デフォルトでは、 ResourceWarning 警告は無視されます。 asyncioのデバッグモードセクションでは、それらを表示する方法について説明しています。