Guido van Rossum、2014年のTulip/Asyncioでのスピーチ スライドを表示 :
タスクとコルーチン
比較:
- res = some_coroutine(...)からのyield
- res = Task(some_coroutine(...))からのyield
タスクはそれを待たずに進行することができます
- 何か他のものを待つのと同じくらいログ
- つまり、からの収量
そして、私は完全に要点を逃しています。
私の観点からは、両方の構成は同一です。
裸のコルーチンの場合-スケジュールが設定されるため、タスクはとにかく作成されます。スケジューラーはタスクを操作するため、コルーチンの呼び出し元のコルーチンは、呼び出し先が完了するまで中断され、自由に実行を続行できるようになります。
Task
の場合-まったく同じ-新しいタスクがスケジュールされ、呼び出し元のコルーチンがその完了を待ちます。
両方の場合でコードが実行される方法の違いは何ですか?また、開発者が実際に考慮する必要がある影響は何ですか?
p.s.
信頼できるソース(GvR、PEP、ドキュメント、コア開発ノート)へのリンクは非常に高く評価されます。
呼び出し側のコルーチンyield from coroutine()
は、関数呼び出しのように感じます(つまり、coroutine()が終了すると再び制御を取得します)。
一方、yield from Task(coroutine())
は、新しいスレッドを作成するように感じます。 Task()
はほぼ瞬時に戻り、coroutine()
が終了する前に呼び出し元が制御を取り戻す可能性が非常に高くなります。
f()
とth = threading.Thread(target=f, args=()); th.start(); th.join()
の違いは明らかですよね?
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
_ になります。
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()
ジェネレータオブジェクトを格納する新しいタスクが作成されます。