2121 pass
2222
2323from AnyQt .QtCore import (
24- Qt , QObject , QMetaObject , QThreadPool , QThread , QRunnable ,
24+ Qt , QObject , QMetaObject , QThreadPool , QThread , QRunnable , QSemaphore ,
2525 QEventLoop , QCoreApplication , QEvent , Q_ARG
2626)
2727
@@ -570,10 +570,12 @@ class FutureSetWatcher(QObject):
570570 doneAll = Signal ()
571571
572572 def __init__ (self , futures = None , * args , ** kwargs ):
573+ # type: (List[Future], ...) -> None
573574 super ().__init__ (* args , ** kwargs )
574575 self .__futures = None
576+ self .__semaphore = None
575577 self .__countdone = 0
576- if futures :
578+ if futures is not None :
577579 self .setFutures (futures )
578580
579581 def setFutures (self , futures ):
@@ -593,22 +595,33 @@ def setFutures(self, futures):
593595 selfweakref = weakref .ref (self )
594596 schedule_emit = methodinvoke (self , "__emitpending" , (int , Future ))
595597
598+ # Semaphore counting the number of future that have enqueued
599+ # done notifications. Used for the `wait` implementation.
600+ self .__semaphore = semaphore = QSemaphore (0 )
601+
596602 for i , future in enumerate (futures ):
597603 self .__futures .append (future )
598604
599605 def on_done (index , f ):
600- selfref = selfweakref () # not safe really
601- if selfref is None :
602- return
603606 try :
604- schedule_emit (index , f )
605- except RuntimeError :
606- # Ignore RuntimeErrors (when C++ side of QObject is deleted)
607- # (? Use QObject.destroyed and remove the done callback ?)
608- pass
607+ selfref = selfweakref () # not safe really
608+ if selfref is None : # pragma: no cover
609+ return
610+ try :
611+ schedule_emit (index , f )
612+ except RuntimeError : # pragma: no cover
613+ # Ignore RuntimeErrors (when C++ side of QObject is deleted)
614+ # (? Use QObject.destroyed and remove the done callback ?)
615+ pass
616+ finally :
617+ semaphore .release ()
609618
610619 future .add_done_callback (partial (on_done , i ))
611620
621+ if not self .__futures :
622+ # `futures` was an empty sequence.
623+ methodinvoke (self , "doneAll" , ())()
624+
612625 @Slot (int , Future )
613626 def __emitpending (self , index , future ):
614627 # type: (int, Future) -> None
@@ -640,10 +653,29 @@ def __emitpending(self, index, future):
640653 def flush (self ):
641654 """
642655 Flush all pending signal emits currently enqueued.
656+
657+ Must only ever be called from the thread this object lives in
658+ (:func:`QObject.thread()`).
643659 """
644- assert QThread .currentThread () is self .thread ()
660+ if QThread .currentThread () is not self .thread ():
661+ raise RuntimeError ("`flush()` called from a wrong thread." )
662+ # NOTE: QEvent.MetaCall is the event implementing the
663+ # `Qt.QueuedConnection` method invocation.
645664 QCoreApplication .sendPostedEvents (self , QEvent .MetaCall )
646665
666+ def wait (self ):
667+ """
668+ Wait for for all the futures to complete and *enqueue* notifications
669+ to this object, but do not emit any signals.
670+
671+ Use `flush()` to emit all signals after a `wait()`
672+ """
673+ if self .__futures is None :
674+ raise RuntimeError ("Futures were not set." )
675+
676+ self .__semaphore .acquire (len (self .__futures ))
677+ self .__semaphore .release (len (self .__futures ))
678+
647679
648680class methodinvoke (object ):
649681 """
0 commit comments