@@ -463,16 +463,20 @@ def _on_completion(f):
463463
464464# This is *not* a @coroutine! It is just an iterator (yielding Futures).
465465def as_completed (fs , * , loop = None , timeout = None ):
466- """Return an iterator whose values, when waited for, are Futures.
466+ """Return an iterator whose values are coroutines.
467+
468+ When waiting for the yielded coroutines you'll get the results (or
469+ exceptions!) of the original Futures (or coroutines), in the order
470+ in which and as soon as they complete.
467471
468472 This differs from PEP 3148; the proper way to use this is:
469473
470474 for f in as_completed(fs):
471475 result = yield from f # The 'yield from' may raise.
472476 # Use result.
473477
474- Raises TimeoutError if the timeout occurs before all Futures are
475- done.
478+ If a timeout is specified, the 'yield from' will raise
479+ TimeoutError when the timeout occurs before all Futures are done.
476480
477481 Note: The futures 'f' are not necessarily members of fs.
478482 """
@@ -481,27 +485,36 @@ def as_completed(fs, *, loop=None, timeout=None):
481485 loop = loop if loop is not None else events .get_event_loop ()
482486 deadline = None if timeout is None else loop .time () + timeout
483487 todo = {async (f , loop = loop ) for f in set (fs )}
484- completed = collections .deque ()
488+ from .queues import Queue # Import here to avoid circular import problem.
489+ done = Queue (loop = loop )
490+ timeout_handle = None
491+
492+ def _on_timeout ():
493+ for f in todo :
494+ f .remove_done_callback (_on_completion )
495+ done .put_nowait (None ) # Queue a dummy value for _wait_for_one().
496+ todo .clear () # Can't do todo.remove(f) in the loop.
497+
498+ def _on_completion (f ):
499+ if not todo :
500+ return # _on_timeout() was here first.
501+ todo .remove (f )
502+ done .put_nowait (f )
503+ if not todo and timeout_handle is not None :
504+ timeout_handle .cancel ()
485505
486506 @coroutine
487507 def _wait_for_one ():
488- while not completed :
489- timeout = None
490- if deadline is not None :
491- timeout = deadline - loop .time ()
492- if timeout < 0 :
493- raise futures .TimeoutError ()
494- done , pending = yield from _wait (
495- todo , timeout , FIRST_COMPLETED , loop )
496- # Multiple callers might be waiting for the same events
497- # and getting the same outcome. Dedupe by updating todo.
498- for f in done :
499- if f in todo :
500- todo .remove (f )
501- completed .append (f )
502- f = completed .popleft ()
503- return f .result () # May raise.
508+ f = yield from done .get ()
509+ if f is None :
510+ # Dummy value from _on_timeout().
511+ raise futures .TimeoutError
512+ return f .result () # May raise f.exception().
504513
514+ for f in todo :
515+ f .add_done_callback (_on_completion )
516+ if todo and timeout is not None :
517+ timeout_handle = loop .call_later (timeout , _on_timeout )
505518 for _ in range (len (todo )):
506519 yield _wait_for_one ()
507520
0 commit comments