asyncio.as_completed是如何工作的

时间:2017-05-18 13:54:07

标签: python python-3.x python-asyncio

阅读this answer,我遇到了asyncio.tasks.as_completed。我不明白这个功能是如何运作的。它被记录为非同步例程,按照它们完成的顺序返回期货。 它创建一个与事件循环关联的队列,为每个未来添加完成回调,然后尝试从队列中获取与期货一样多的项目。

代码的核心如下:

    def _on_completion(f):
        if not todo:
            return  # _on_timeout() was here first.
        todo.remove(f)
        done.put_nowait(f)
        if not todo and timeout_handle is not None:
            timeout_handle.cancel()

    @coroutine
    def _wait_for_one():
        f = yield from done.get()
        if f is None:
            # Dummy value from _on_timeout().
            raise futures.TimeoutError
        return f.result()  # May raise f.exception().

    for f in todo:
        f.add_done_callback(_on_completion)
    if todo and timeout is not None:
        timeout_handle = loop.call_later(timeout, _on_timeout)
    for _ in range(len(todo)):
        yield _wait_for_one()

我想了解这段代码的工作原理。我最大的问题是:

  • 循环实际运行在哪里。我没有看到对loop.run_until_cobmplete或loop.run_forever的任何调用。那么循环如何取得进展呢?

  • 方法文档说该方法返回期货。你可以称之为

    对于as_completed(期货)中的f:     结果=来自f的收益

我无法与_wait_for_one中的返回f.result行进行协调。记录的调用约定是否正确?如果是这样,那么收益率来自哪里?

1 个答案:

答案 0 :(得分:3)

您复制的代码缺少标题部分,这非常重要。

# This is *not* a @coroutine!  It is just an iterator (yielding Futures).
def as_completed(fs, *, loop=None, timeout=None):
    """Return an iterator whose values are coroutines.

    When waiting for the yielded coroutines you'll get the results (or
    exceptions!) of the original Futures (or coroutines), in the order
    in which and as soon as they complete.

    This differs from PEP 3148; the proper way to use this is:

        for f in as_completed(fs):
            result = yield from f  # The 'yield from' may raise.
            # Use result.

    If a timeout is specified, the 'yield from' will raise
    TimeoutError when the timeout occurs before all Futures are done.

    Note: The futures 'f' are not necessarily members of fs.
    """
    if futures.isfuture(fs) or coroutines.iscoroutine(fs):
        raise TypeError("expect a list of futures, not %s" % type(fs).__name__)
    loop = loop if loop is not None else events.get_event_loop()
    todo = {ensure_future(f, loop=loop) for f in set(fs)}
    from .queues import Queue  # Import here to avoid circular import problem.
    done = Queue(loop=loop)
    timeout_handle = None

    def _on_timeout():
        for f in todo:
            f.remove_done_callback(_on_completion)
            done.put_nowait(None)  # Queue a dummy value for _wait_for_one().
        todo.clear()  # Can't do todo.remove(f) in the loop.

    def _on_completion(f):
        if not todo:
            return  # _on_timeout() was here first.
        todo.remove(f)
        done.put_nowait(f)
        if not todo and timeout_handle is not None:
            timeout_handle.cancel()

    @coroutine
    def _wait_for_one():
        f = yield from done.get()
        if f is None:
            # Dummy value from _on_timeout().
            raise futures.TimeoutError
        return f.result()  # May raise f.exception().

    for f in todo:
        f.add_done_callback(_on_completion)
    if todo and timeout is not None:
        timeout_handle = loop.call_later(timeout, _on_timeout)
    for _ in range(len(todo)):
        yield _wait_for_one()

[循环实际在哪里运行?]

为简单起见,假设超时设置为无。

as_completed期待一个可迭代的期货,而不是协程。所以这个期货已经绑定到循环并计划执行。换句话说,那些期货是loop.create_task或asyncio.ensure_futures的输出(这是明确写的)。 所以循环已经“运行”了它们,当它们完成时,它们未来的.done()方法将返回True。

然后创建“完成”队列。请注意,“done”队列是asyncio.queue的一个istance,即使用循环«实现阻塞方法(.get,.put)的队列。

通过“todo = {...”这一行,每个协程的未来(这是fs的一个元素)都包含在另一个未来»绑定到循环«,而这最后的未来的done_callback被设置为调用_on_completion函数

当循环完成协程的执行时,将调用_on_completion函数,协程的期限在设置为as_completed函数的“fs”中传递。

_on_completion函数从todo集中删除“我们的未来”,并将其结果(即未来在“fs”集中的协程)放入完成队列中。 换句话说,as_completed函数所做的就是使用done_callback附加这些期货,以便将原始未来的结果移入完成队列。

然后,对于len(fs)== len(todo)次,as_completed函数产生一个协程,阻止“yield from done.get()”,等待_on_completed(或_on_timeout)函数放置结果完成完成队列。

由as_completed调用者执行的“yield from”将等待结果出现在done队列中。

[收益来自哪里?]

它来自todo是asyncio.queue的事实,所以你可以(asyncio-)阻塞,直到队列中的值为.put()。