4

Python 3 asyncio フレームワークを使用して、定期的な実行 (簡潔にするために実際のスリープ/遅延は省略) のさまざまなパターンを評価していますが、動作が異なる 2 つのコードがあり、その理由を説明できません。自分自身を再帰的に呼び出すために使用する最初のバージョンは、yield from予想どおり、約 1000 回の反復でスタックを使い果たしました。2 番目のバージョンは、コルーチンを再帰的に呼び出しますが、実際のイベント ループの実行を委譲asyncio.asyncし、スタックを使い果たしません。スタックが 2 番目のバージョンで使用されていない理由を詳しく説明できますか? このコルーチンを実行する 2 つの方法の違いは何ですか?

最初のバージョン (からの利回り):

@asyncio.coroutine
def call_self(self, i):
    print('calling self', i)
    yield from self.call_self(i + 1)

2 番目のバージョン (asyncio.async):

@asyncio.coroutine
def call_self(self, i):
    print('calling self', i)
    asyncio.async(self.call_self(i + 1))
4

1 に答える 1

11

を使用した最初の例では、 の再帰呼び出しが戻るまで、yield fromの各インスタンスを実際にブロックします。これは、スタック スペースがなくなるまでコール スタックが増加し続けることを意味します。おっしゃるとおり、これは当たり前の行動です。call_selfcall_self

を使用した 2 番目の例では、asyncio.asyncどこにもブロックされません。したがって、 の各インスタンスはcall_self実行後すぐに終了しますasyncio.async(...)。つまり、スタックが無限に大きくなることはありません。つまり、スタックを使い果たしません。代わりに、asyncio.asyncスケジュールcall_selfは、イベント ループの繰り返しで実行されるようになりますasyncio.Task

は次のとおり__init__です。Task

def __init__(self, coro, *, loop=None):
    assert iscoroutine(coro), repr(coro)  # Not a coroutine function!
    super().__init__(loop=loop)
    self._coro = iter(coro)  # Use the iterator just in case.
    self._fut_waiter = None
    self._must_cancel = False
    self._loop.call_soon(self._step)  # This schedules the coroutine to be run
    self.__class__._all_tasks.add(self)

への呼び出しself._loop.call_soon(self._step)は、実際にコルーチンを実行させるものです。非ブロッキングの方法で発生しているため、呼び出しスタックがコンストラクターcall_selfへの呼び出しを超えて成長することはありません。Taskの次のインスタンスはcall_self、次の反復でイベント ループによって開始されます (イベント ループでcall_self他に何も実行されていないと仮定すると、前のインスタンスが戻るとすぐに開始されます)。完全に前のcall_selfインスタンスのコンテキスト外です。

于 2014-10-26T22:19:23.817 に答える