web-dev-qa-db-ja.com

コルーチンを同期的に呼び出す

次の非常に一般的な状況を想像してみてください。長くて複雑な関数を記述し、コードの一部を再利用や可読性のために別の関数に抽出する必要があることに気づきました。通常、この追加の関数呼び出しは、プログラムのセマンティクスを変更しません。

ただし、関数がコルーチンであり、抽出するコードに少なくとも1つの非同期呼び出しが含まれていると想像してください。これを別の関数に抽出すると、コルーチンが生成する新しいポイントを挿入することでプログラムのセマンティクスが突然変更され、イベントループが制御を引き継ぎ、その間に他のコルーチンをスケジュールできます。

前の例:

_async def complicated_func():
    foo()
    bar()
    await baz()
_

後の例:

_async def complicated_func():
    foo()
    await extracted_func()

async def extracted_func():
    bar()
    await baz()
_

前の例では、_complicated_func_は、foo()の呼び出しとbar()の呼び出しの間で中断されないことが保証されています。リファクタリング後、この保証は失われます。

私の質問はこれです:コードがインラインであるかのようにすぐに実行されるようにextracted_func()を呼び出すことは可能ですか?または、プログラムのセマンティクスを変更せずに、このような一般的なリファクタリングタスクを実行する他の方法はありますか?

13
Max Tet

リファクタリング後、この保証は失われます。

実際にはそうではありません。

コードがインラインであるかのようにすぐに実行されるようにextracted_func()を呼び出すことは可能ですか?

それはすでにそうです。

await some_coroutine()は、some_coroutineがイベントループに制御を戻す可能性が高いことを意味しますが、実際に将来(I/O操作など)を待つまではそうしません。

この例を考えてみましょう。

import asyncio

async def coro():
    print(1)
    await asyncio.sleep(0)
    print(3)

async def main():
    loop.call_soon(print, 2)
    await coro()

loop = asyncio.get_event_loop()
loop.run_until_complete(main())

213の間に期待どおりに出力されることに注目してください。

これは、次のようなコードを記述することでイベントループをフリーズできることも意味します。

async def coro():
    return

async def main():
    while True:
        await coro()

この場合、イベントループは別のタスクを実行する機会を得ることはありません。

9
Vincent