memo

2016-06-08

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) で行うようになっている。