OeufsDePie / FAQt

FAQt (Frequently Asked Questions about Qt)
0 stars 0 forks source link

PyQt: QThread and Signals #4

Open ghost opened 9 years ago

ghost commented 9 years ago

I have been trying to use QThread and Signals together. Having a secondary class, moved to a new thread thanks to QThread, that can communicate with the main thread through the Signals/Slots system. Here is a basic usage example, adapted to PyQt5 from this SO question, where I corrected the suggested problem (connect the start signal of simulThread), and also moved everything out of any GUI component :

import time, sys
from PyQt5.QtCore  import *
from PyQt5.QtWidgets import *

class SimulRunner(QObject):
    'Object that will be run in a secondary thread'

    stepIncreased = pyqtSignal(int)

    nextStep = pyqtSignal()

    def __init__(self):
        super(SimulRunner, self).__init__()
        self._step = 0
        self._isRunning = True
        self.nextStep.connect(self.longRunning)

    @pyqtSlot()
    def longRunning(self):
        print("Thread running ? " + str(self._isRunning))
        if self._isRunning == True: # A simple while loop would
                                    # prevent the Qt event loop to
                                    # treat the stop signal
            # Increase counter
            self._step += 1
            self.stepIncreased.emit(self._step)
            time.sleep(0.5)
            # Trying to give the control back to the event loop for a
            # moment, hoping that Stop signals will be treated before
            # the recursive call
            self.nextStep.emit()

            # This alternative doesn't change the 'stop' button
            # problem
            'self.longRunning()'

    @pyqtSlot()
    def stop(self):
        print("Stopping thread")
        self._isRunning = False

class SimulationUi(QObject):
    'Main thread'

    startSignal = pyqtSignal()
    stopSignal = pyqtSignal()

    def __init__(self):
        super(SimulationUi, self).__init__()
        # Setup counter
        self.counter = 0
        # Create thread and connect start signals
        self.simulRunner = SimulRunner()
        self.simulThread = QThread()
        self.simulRunner.moveToThread(self.simulThread)
        # Connect with Thread 
        self.startSignal.connect(self.simulThread.start) # Does work
        self.simulThread.started.connect(self.simulRunner.longRunning) # Does work
        # Connect with runner Object
        self.simulRunner.stepIncreased.connect(self.showProgress) # Only works with 'app = QApplication(sys.argv)
        self.stopSignal.connect(self.simulRunner.stop) # Does NOT work

    @pyqtSlot(int)
    def showProgress(self, integer):
        print(integer)

    def start(self):
        self.startSignal.emit()

    def stop(self):
        self.stopSignal.emit()

if __name__ == '__main__':
    app = QApplication(sys.argv) # This single line is needed for the
                                 # 'showProgress' callback to work
    simul = SimulationUi()
    simul.start()
    input("Hit 'Return' a first time to stop the counter.\n")
    simul.stop()
    input("Now the thread should have stop. Return to terminate.\n")

The results are:

My questions are:

Thank you for the help.

mpizenberg commented 9 years ago

app = QApplication(... is needed apparently since QEventLoop: Cannot be used without QApplication and QEventLoop "provides a means of entering and leaving an event loop" according to the documentation.