web-dev-qa-db-ja.com

コルーチンからの収量とタスクからの収量

Guido van Rossum、2014年のTulip/Asyncioでのスピーチ スライドを表示

タスクとコルーチン

  • 比較:

    • res = some_coroutine(...)からのyield
    • res = Task(some_coroutine(...))からのyield
  • タスクはそれを待たずに進行することができます

    • 何か他のものを待つのと同じくらいログ
      • つまり、からの収量

そして、私は完全に要点を逃しています。

私の観点からは、両方の構成は同一です。

裸のコルーチンの場合-スケジュールが設定されるため、タスクはとにかく作成されます。スケジューラーはタスクを操作するため、コルーチンの呼び出し元のコルーチンは、呼び出し先が完了するまで中断され、自由に実行を続行できるようになります。

Taskの場合-まったく同じ-新しいタスクがスケジュールされ、呼び出し元のコルーチンがその完了を待ちます。

両方の場合でコードが実行される方法の違いは何ですか?また、開発者が実際に考慮する必要がある影響は何ですか?

p.s.
信頼できるソース(GvR、PEP、ドキュメント、コア開発ノート)へのリンクは非常に高く評価されます。

24
Gill Bates

呼び出し側のコルーチンyield from coroutine()は、関数呼び出しのように感じます(つまり、coroutine()が終了すると再び制御を取得します)。

一方、yield from Task(coroutine())は、新しいスレッドを作成するように感じます。 Task()はほぼ瞬時に戻り、coroutine()が終了する前に呼び出し元が制御を取り戻す可能性が非常に高くなります。

f()th = threading.Thread(target=f, args=()); th.start(); th.join()の違いは明らかですよね?

27
Andrew Svetlov

asyncio.Task(coro())を使用するポイントは、do n'tcoroを明示的に待機したいが、coroを実行したい場合です。他のタスクを待つ間、バックグラウンドで。それがGuidoのスライドの意味です

[A] Taskはそれを待たずに進歩することができます...何か他のものを待つ限り

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

_import asyncio

@asyncio.coroutine
def test1():
    print("in test1")


@asyncio.coroutine
def dummy():
    yield from asyncio.sleep(1)
    print("dummy ran")


@asyncio.coroutine
def main():
    test1()
    yield from dummy()

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

出力:

_dummy ran
_

ご覧のとおり、_test1_を明示的に呼び出さなかったため、_yield from_が実際に実行されることはありませんでした。

ここで、_asyncio.async_を使用してTaskインスタンスを_test1_の周りにラップすると、結果は異なります。

_import asyncio

@asyncio.coroutine
def test1():
    print("in test1")


@asyncio.coroutine
def dummy():
    yield from asyncio.sleep(1)
    print("dummy ran")


@asyncio.coroutine
def main():
    asyncio.async(test1())
    yield from dummy()

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

出力:

_in test1
dummy ran
_

したがって、yield from asyncio.async(coro())よりも遅いため、実際にはyield from coro()を使用する実際的な理由はありません。 coroを内部のasyncioスケジューラーに追加するオーバーヘッドが発生しますが、_yield from_を使用すると、とにかくcoroが実行されることが保証されるため、これは必要ありません。コルーチンを呼び出して終了するのを待つだけの場合は、コルーチンを直接_yield from_します。

サイドノート:

Taskの代わりに_asyncio.async_ *を直接使用しています ドキュメントで推奨されているため

Taskインスタンスを直接作成しないでください。async()関数またはBaseEventLoop.create_task()メソッドを使用してください。

* Python 3.4.4以降、_asyncio.async_は非推奨になり、 _asyncio.ensure_future_ になります。

13
dano

PEP 380で説明されているように、式res = yield from f()からyieldを導入した受け入れられたPEPドキュメントは、次のループのアイデアに由来します。

_for res in f():
    yield res
_

これにより、状況が非常に明確になります。f()some_coroutine()の場合、コルーチンが実行されます。一方、f()Task(some_coroutine())の場合は、代わりに_Task.__init___が実行されます。 some_coroutine()は実行されず、新しく作成されたジェネレーターのみが最初の引数として_Task.__init___に渡されます。

結論:

  • res = yield from some_coroutine() =>コルーチンは実行を継続し、次の値を返します
  • res = yield from Task(some_coroutine()) =>実行されていないsome_coroutine()ジェネレータオブジェクトを格納する新しいタスクが作成されます。
3
hdante