future, yield from そして Task について
先日 は、 Coroutine2Future というものを実装して coroutine の説明をしたが、 実際の coroutine/future/Task についても見てみる。
yield from?
asyncio の croutine の使い方は以下のような感じになる。
import asyncio @asyncio.coroutine def coro(): fut = asyncio.Future() loop = asyncio.get_event_loop() loop.call_later(1, fut.set_result, None) yield from fut return 42 loop = asyncio.get_event_loop() print(loop.run_until_complete(coro()))
さて、ここでよく見るとおかしな気がするのだが、 Future は yield ではなく yield from する必要がある。 (yield だけだと、 Task が RuntimeError を起こす。) yield from というのは普通は他の
generator/coroutine に対して使うものだ。 Future を yield from するとはどういうこと?
答え
Future は Iterable なので yield from できます。
Future.__iter__() の実装は以下のようになっている。
def __iter__(self): if not self.done(): self._blocking = True yield self # This tells Task to wait for completion. assert self.done(), "yield from wasn't used with future" return self.result() # May raise too.
yield from はこれを実行し、ここから出てきたもの (yield self の部分) を coroutine 実行側に戻すことになる。
これ意味あるの?
yield self とはつまり yield from された future と同じものを返しているということだ。 結局同じものが出てくるのであれば、わざわざ yield from を強制して __iter__() を通らせる意味はあるのだろうか?
想像だけれども、 coroutine (generator) と future でどちらも yield from を使うように統一したかったのかもしれない。
また、動作の面の違いとしては、 coro.send() で再開するのは上記の yield self の箇所からになり、 yield from の返り値としては return self.result() になるので、 coro.send() には future
の結果をわざわざ渡す必要がない、というのがある。
実際に、 Task._wakeup() (出てきた future の callback として使われるメソッド) では future の結果を引き回すようなことはしておらず、 その後の ._step() での coroutine の再開は常に coro.send(None)
で行うようになっている。