How to use QTimer inside QThread which uses QWaitC

2019-04-13 12:01发布

问题:

I'm using pyside but (I think) is a generic Qt question.

I know that QThread implementation calls ._exec() method so we should have an event loop on a started QThread. This way we can use QTimer on that thread (I've done this and it works perfectly). My problem is when QWaitCondition is also used, I'd like to have a "consumer" thread with a infinite loop waiting to be notify (from producers) on the QWaitCondition. The problem I have is that with this design I cannot use QTimer inside the consumer Thread.

This is a snippet of the scenario I'm trying to explain:

from PySide import QtGui
from PySide import QtCore
import sys

class MainWindow(QtGui.QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()

        self.button = QtGui.QPushButton(self)
        self.button.setText("Periodical")
        self.button.clicked.connect(self.periodical_call)

        self.thread = QtCore.QThread()
        self.worker = Worker()
        self.worker.moveToThread(self.thread)
        self.thread.started.connect(self.worker.loop)
        self.thread.start()

    def closeEvent(self, x):
        self.worker.stop()
        self.thread.quit()
        self.thread.wait()

    def periodical_call(self):
        self.worker.do_stuff("main window") # this works
        self.timer = QtCore.QTimer()
        self.timer.timeout.connect(self.do_stuff) # this also works
        self.timer.start(2000)

    def do_stuff(self):
        self.worker.do_stuff("timer main window")

class Worker(QtCore.QObject):
    def do_stuff_timer(self):
        do_stuff("timer worker")

    def do_stuff(self, origin):
        self.origin = origin
        self.wait.wakeOne()

    def stop(self):
        self._exit = True
        self.wait.wakeAll()

    def loop(self):
        self.wait = QtCore.QWaitCondition()
        self.mutex = QtCore.QMutex()
        self._exit = False
        while not self._exit:
            self.wait.wait(self.mutex)

            print "loop from %s" % (self.origin,)

            self.timer = QtCore.QTimer()
            self.timer.setSingleShot(True)
            self.timer.timeout.connect(self.do_stuff_timer)
            self.timer.start(1000) # <---- this doesn't work

if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    frame = MainWindow()
    frame.show()
    sys.exit(app.exec_())

Once you click the button we obtain an output like this:

loop from main window                    
loop from timer main window
loop from timer main window
loop from timer main window
...

This means that the QTimer created inside loop() method is never executed by the event loop.

If I change the design from QWaitCondition to Signals (which is better design imho) the QTimer works, but I'd like to know why they are not working when QWaitCondition is used.

回答1:

To still process events in a long running task (aka a continuous loop) you need to call QCoreApplication::processEvents().

This will essentially go through all of the queued up slots for your thread.

Calling this function is also necessary for signals (if they are a QueuedConnection signal/slot connection) to make it out of the current thread and into another one.


For PySides, you will need to call PySide.QtCore.QCoreApplication.processEvents()



回答2:

Your method loop completely occupies thread. It doesn't return control to event loop. Timer sends its events to event loop which doesn't gain control.
IMO your wile loop is faulty.

One way to fix it is add QApplication.processEvents() in loop (bad approach).

I think you want something else, here are my corrections:

def loop(self):
    self.timer = QtCore.QTimer()
    self.timer.setSingleShot(False)
    self.timer.timeout.connect(self.do_stuff_timer)
    self.timer.start(1000)

def stop(self):
    self.timer.stop()

this will invoke do_stuff_timer every second until you will call stop.