7

I am trying to understand how to use signaling from a Qthread back to the Gui interface that started.

Setup: I have a process (a simulation) that needs to run almost indefinitely (or at least for very long stretches of time)., While it runs, it carries out various computations, amd some of the results must be sent back to the GUI, which will display them appropriately in real time. I am using PyQt for the GUI. I originally tried using python's threading module, then switched to QThreads after reading several posts both here on SO and elsewhere.

According to this post on the Qt Blog You're doing it wrong, the preferred way to use QThread is by creating a QObject and then moving it to a Qthread. So I followed the advice inBackground thread with QThread in PyQt"> this SO question and tried a simple test app (code below): it opens up a simple GUI, let you start the background process, and it issupposed to update the step value in a spinbox.

But it does not work. The GUI is never updated. What am I doing wrong?

import time, sys
from PyQt4.QtCore  import *
from PyQt4.QtGui import * 

class SimulRunner(QObject):
    'Object managing the simulation'

    stepIncreased = pyqtSignal(int, name = 'stepIncreased')
    def __init__(self):
        super(SimulRunner, self).__init__()
        self._step = 0
        self._isRunning = True
        self._maxSteps = 20

    def longRunning(self):
        while self._step  < self._maxSteps  and self._isRunning == True:
            self._step += 1
            self.stepIncreased.emit(self._step)
            time.sleep(0.1)

    def stop(self):
        self._isRunning = False

class SimulationUi(QDialog):
    'PyQt interface'

    def __init__(self):
        super(SimulationUi, self).__init__()

        self.goButton = QPushButton('Go')
        self.stopButton = QPushButton('Stop')
        self.currentStep = QSpinBox()

        self.layout = QHBoxLayout()
        self.layout.addWidget(self.goButton)
        self.layout.addWidget(self.stopButton)
        self.layout.addWidget(self.currentStep)
        self.setLayout(self.layout)

        self.simulRunner = SimulRunner()
        self.simulThread = QThread()
        self.simulRunner.moveToThread(self.simulThread)
        self.simulRunner.stepIncreased.connect(self.currentStep.setValue)


        self.connect(self.stopButton, SIGNAL('clicked()'), self.simulRunner.stop)
        self.connect(self.goButton, SIGNAL('clicked()'), self.simulThread.start)
        self.connect(self.simulRunner,SIGNAL('stepIncreased'), self.currentStep.setValue)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    simul = SimulationUi()
    simul.show()
    sys.exit(app.exec_())
stefano
  • 769
  • 2
  • 10
  • 24

1 Answers1

7

The problem here is simple: your SimulRunner never gets sent a signal that causes it to start its work. One way of doing that would be to connect it to the started signal of the Thread.

Also, in python you should use the new-style way of connecting signals:

...
self.simulRunner = SimulRunner()
self.simulThread = QThread()
self.simulRunner.moveToThread(self.simulThread)
self.simulRunner.stepIncreased.connect(self.currentStep.setValue)
self.stopButton.clicked.connect(self.simulRunner.stop)
self.goButton.clicked.connect(self.simulThread.start)
# start the execution loop with the thread:
self.simulThread.started.connect(self.simulRunner.longRunning)
...
mata
  • 67,110
  • 10
  • 163
  • 162
  • 1
    You're absolutely right, I just realized the mistake. I cut and pasted code from a previous version in which I was subclassing Qthread instead of using moveToThread, and completely forgot about the started() method. Thanks for pointing it out. – stefano Apr 26 '13 at 18:34
  • this is a very interesting example. However follow this suggestion the thread starts but the stop button does not stop it. It would be great to have a working example. And maybe a Go button that can restart the whole thing – Fabrizio Aug 08 '13 at 09:49
  • The stop just sends a signal, but as `simulThread` is busy running `longRunning`, that signal can only be processed after that method has exited, that's why it doesn't really stop the counter. To do so, `stop` would havet to be called from a different thread (e.g. the main gui thread). To restart the counter, you'd need to reset the `_isRunning` flag if it was stopped, but that's beyond the original scope of the question. Nevertheless, i've posted a little more complete example [here](http://pastebin.com/kmvLb6Y2) (there would still be some things that could be improved tough...) – mata Aug 08 '13 at 20:42
  • When I close the QDialog appears that error message: QThread: Destroyed while thread is still running. Is there a problem? – GSandro_Strongs Oct 16 '15 at 00:51
  • Are you sure this is actually working the way you think it is? `self.simulRunner` is in fact owned by the thread that has created it (since the instance of the class whose member it is is there). You should create the instance of the `simulRunner` (without the `self`), connect all you want to connect (including adding `self.simulThread.finished.connect(simulRunner.deleteLater) to clean up properly), move it to `self.simulThread` and that's it. At least this is the way the C++ API (and the documentation) tells you to do it. – rbaleksandar Feb 14 '16 at 21:02