18.5.9. asyncioで開発—Pythonドキュメント

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

18.5.9。 asyncioで開発する

非同期プログラミングは、従来の「シーケンシャル」プログラミングとは異なります。 このページでは、一般的なトラップを一覧表示し、それらを回避する方法について説明します。

18.5.9.1。 asyncioのデバッグモード

asyncio の実装は、パフォーマンスのために作成されています。 非同期コードの開発を容易にするために、デバッグモードを有効にすることをお勧めします。

アプリケーションのすべてのデバッグチェックを有効にするには:

  • 環境変数 PYTHONASYNCIODEBUG1に設定するか、 AbstractEventLoop.set_debug()を呼び出すことにより、非同期デバッグモードをグローバルに有効にします。
  • 非同期ロガーのログレベルをlogging.DEBUGに設定します。 たとえば、起動時にlogging.basicConfig(level=logging.DEBUG)を呼び出します。
  • 警告モジュールを構成して、 ResourceWarning 警告を表示します。 たとえば、Pythonの-Wdefaultコマンドラインオプションを使用してそれらを表示します。

デバッグチェックの例:

も参照してください

AbstractEventLoop.set_debug()メソッドと非同期ロガー


18.5.9.2。 キャンセル

タスクのキャンセルは、従来のプログラミングでは一般的ではありません。 非同期プログラミングでは、それは一般的なことであるだけでなく、それを処理するためのコードを準備する必要があります。

先物とタスクは、 Future.cancel()メソッドを使用して明示的にキャンセルできます。 wait_for()関数は、タイムアウトが発生すると、待機中のタスクをキャンセルします。 タスクを間接的にキャンセルできるケースは他にもたくさんあります。

フューチャーがキャンセルされた場合は、 Futureset_result()または set_exception()メソッドを呼び出さないでください。例外で失敗します。 たとえば、次のように記述します。

if not fut.cancelled():
    fut.set_result('done')

AbstractEventLoop.call_soon()を使用して、futureの set_result()または set_exception()メソッドへの呼び出しを直接スケジュールしないでください。そのメソッドが呼び出される前にキャンセルされました。

将来を待つ場合は、無駄な操作を避けるために、将来がキャンセルされたかどうかを早めに確認する必要があります。 例:

@coroutine
def slow_operation(fut):
    if fut.cancelled():
        return
    # ... slow computation ...
    yield from fut
    # ...

shield()機能を使用して、キャンセルを無視することもできます。


18.5.9.3。 並行性とマルチスレッド

イベントループはスレッドで実行され、同じスレッドですべてのコールバックとタスクを実行します。 タスクがイベントループで実行されている間、他のタスクは同じスレッドで実行されていません。 ただし、タスクがyield fromを使用すると、タスクは一時停止され、イベントループが次のタスクを実行します。

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

loop.call_soon_threadsafe(callback, *args)

ほとんどのasyncioオブジェクトはスレッドセーフではありません。 イベントループの外側のオブジェクトにアクセスする場合にのみ心配する必要があります。 たとえば、futureをキャンセルするには、その Future.cancel()メソッドを直接呼び出さないでください。ただし、次のようにします。

loop.call_soon_threadsafe(fut.cancel)

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

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

future = asyncio.run_coroutine_threadsafe(coro_func(), loop)
result = future.result(timeout)  # Wait for the result with a timeout

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 のデバッグモードを有効にして、警告をログに記録して検出します。

バグの例:

import asyncio

@asyncio.coroutine
def test():
    print("never scheduled")

test()

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

Coroutine test() at test.py:3 was never yielded from
Coroutine object created at (most recent call last):
  File "test.py", line 7, in <module>
    test()

修正は、コルーチンオブジェクトを使用して sure_future()関数または AbstractEventLoop.create_task()メソッドを呼び出すことです。

も参照してください

保留中のタスクが破棄されました


18.5.9.7。 消費されたことのない例外を検出する

Pythonは通常、未処理の例外に対して sys.excepthook()を呼び出します。 Future.set_exception()が呼び出されたが、例外が消費されなかった場合、 sys.excepthook()は呼び出されません。 代わりに、ガベージコレクターによってfutureが削除されると、ログが発行されます。例外が発生したトレースバックがあります。

未処理の例外の例:

import asyncio

@asyncio.coroutine
def bug():
    raise Exception("not consumed")

loop = asyncio.get_event_loop()
asyncio.ensure_future(bug())
loop.run_forever()
loop.close()

出力:

Task exception was never retrieved
future: <Task finished coro=<coro() done, defined at asyncio/coroutines.py:139> exception=Exception('not consumed',)>
Traceback (most recent call last):
  File "asyncio/tasks.py", line 237, in _step
    result = next(coro)
  File "asyncio/coroutines.py", line 141, in coro
    res = func(*args, **kw)
  File "test.py", line 5, in bug
    raise Exception("not consumed")
Exception: not consumed

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

Task exception was never retrieved
future: <Task finished coro=<bug() done, defined at test.py:3> exception=Exception('not consumed',) created at test.py:8>
source_traceback: Object created at (most recent call last):
  File "test.py", line 8, in <module>
    asyncio.ensure_future(bug())
Traceback (most recent call last):
  File "asyncio/tasks.py", line 237, in _step
    result = next(coro)
  File "asyncio/coroutines.py", line 79, in __next__
    return next(self.gen)
  File "asyncio/coroutines.py", line 141, in coro
    res = func(*args, **kw)
  File "test.py", line 5, in bug
    raise Exception("not consumed")
Exception: not consumed

この問題を修正するには、さまざまなオプションがあります。 最初のオプションは、コルーチンを別のコルーチンにチェーンし、従来のtry / exceptを使用することです。

@asyncio.coroutine
def handle_exception():
    try:
        yield from bug()
    except Exception:
        print("exception consumed")

loop = asyncio.get_event_loop()
asyncio.ensure_future(handle_exception())
loop.run_forever()
loop.close()

別のオプションは、 AbstractEventLoop.run_until_complete()関数を使用することです。

task = asyncio.ensure_future(bug())
try:
    loop.run_until_complete(task)
except Exception:
    print("exception consumed")

も参照してください

Future.exception()メソッド。


18.5.9.8。 コルーチンを正しくチェーンする

コルーチン関数が他のコルーチン関数およびタスクを呼び出す場合、それらはyield fromで明示的にチェーンする必要があります。 それ以外の場合、実行は順次であることが保証されません。

asyncio.sleep()を使用して低速操作をシミュレートするさまざまなバグの例:

import asyncio

@asyncio.coroutine
def create():
    yield from asyncio.sleep(3.0)
    print("(1) create file")

@asyncio.coroutine
def write():
    yield from asyncio.sleep(1.0)
    print("(2) write into file")

@asyncio.coroutine
def close():
    print("(3) close file")

@asyncio.coroutine
def test():
    asyncio.ensure_future(create())
    asyncio.ensure_future(write())
    asyncio.ensure_future(close())
    yield from asyncio.sleep(2.0)
    loop.stop()

loop = asyncio.get_event_loop()
asyncio.ensure_future(test())
loop.run_forever()
print("Pending tasks at exit: %s" % asyncio.Task.all_tasks(loop))
loop.close()

期待される出力:

(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.coroutine
def test():
    yield from asyncio.ensure_future(create())
    yield from asyncio.ensure_future(write())
    yield from asyncio.ensure_future(close())
    yield from asyncio.sleep(2.0)
    loop.stop()

またはasyncio.ensure_future()なし:

@asyncio.coroutine
def test():
    yield from create()
    yield from write()
    yield from close()
    yield from asyncio.sleep(2.0)
    loop.stop()

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のデバッグモードセクションでは、それらを表示する方法について説明しています。