1

I'm creating a graphical user interface with PyQt5. I want to send a signal to a running thread to change parameters while it is carrying out a certain process.

I created a simple code to demonstrate what I intend to achieve. The code below generates a basic GUI with two push buttons. When the first button is pressed, a thread is created and started, which prints a string every second. When the second button is pressed, the displayed string is modified and replaced with another string.

My issue is that the string is never changed, and I'm not sure why.

import sys
from time import sleep

from PyQt5.QtCore import Qt, QMutex
from PyQt5.QtWidgets import (
    QApplication,
    QLabel,
    QMainWindow,
    QPushButton,
    QVBoxLayout,
    QWidget,
)

from PyQt5.QtCore import QObject, QThread, pyqtSignal

class Worker(QObject):
    finished = pyqtSignal()

    def __init__(self, parent=None):
        super(Worker, self).__init__()
        self.mutex = QMutex()
        self.stringToDisplay = 'Holà'

    def update(newStringToDisplay):
        self.mutex.lock()
        self.stringToDisplay = newStringToDisplay
        self.mutex.unlock()

    def run(self):
        """Long-running task."""
        while 1:
            sleep(1)
            self.mutex.lock()
            print(self.stringToDisplay)
            self.mutex.unlock()

        self.finished.emit()


class Window(QMainWindow):

    updateSignal = pyqtSignal(str)

    def __init__(self, parent=None):
        super().__init__(parent)
        self.setupUi()

    def setupUi(self):
        self.setWindowTitle("GUI")
        self.resize(300, 150)
        self.button2runThread = QPushButton('Click to start the thread', self)
        self.button2runThread.clicked.connect(self.runThread)

        self.button2updateParams = QPushButton('Click to update parameter in the running thread', self)
        self.button2updateParams.clicked.connect(self.updateThread)

        layout = QVBoxLayout()
        layout.addWidget(self.button2runThread)
        layout.addWidget(self.button2updateParams)
        
        self.centralWidget = QWidget()
        self.setCentralWidget(self.centralWidget)
        self.centralWidget.setLayout(layout)

    def updateThread(self):
        self.updateSignal.emit('My new string')

    def runThread(self):
        self.thread = QThread()
        self.worker = Worker()
        self.worker.moveToThread(self.thread)
        self.thread.started.connect(self.worker.run)
        self.worker.finished.connect(self.thread.quit)
        self.worker.finished.connect(self.worker.deleteLater)
        self.thread.finished.connect(self.thread.deleteLater)
        
        self.updateSignal.connect(self.worker.update)

        self.thread.start()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    win = Window()
    win.show()
    sys.exit(app.exec())
ocha67
  • 21
  • 4
  • `def update(newStringToDisplay):` Shouldn't this have a `self` in it? – matt Jun 15 '22 at 10:18
  • You are correct. I modified my code but it's still not working. – ocha67 Jun 15 '22 at 10:38
  • Did you check to make sure your update method is being called and completing ok. The other mistake you had seems like it would have caused an error preventing the method from every being called. – matt Jun 15 '22 at 10:47
  • Thank you, Matt; I was able to identify the problem. – ocha67 Jun 15 '22 at 11:43

1 Answers1

1

I was able to pinpoint the issue. I connected the signal before moving the Worker to the thread.

Here's the part of code that was causing the issue:

    self.thread = QThread()
    self.worker = Worker()

    self.worker.moveToThread(self.thread) # 1
    # ...
    # ...
    self.updateSignal.connect(self.worker.update) # must be called before 1

and the complete code:

import sys
from time import sleep

from PyQt5.QtCore import Qt, QMutex
from PyQt5.QtWidgets import (
    QApplication,
    QLabel,
    QMainWindow,
    QPushButton,
    QVBoxLayout,
    QWidget,
)

from PyQt5.QtCore import QObject, QThread, pyqtSignal

class Worker(QObject):
    finished = pyqtSignal()

    def __init__(self, parent=None):
        super(Worker, self).__init__()
        self.mutex = QMutex()
        self.stringToDisplay = 'Holà'

    def update(self, newStringToDisplay):
        self.mutex.lock()
        self.stringToDisplay = newStringToDisplay
        self.mutex.unlock()


    def run(self):
        """Long-running task."""
        while 1:
            sleep(1)
            self.mutex.lock()
            print(self.stringToDisplay)
            self.mutex.unlock()

        self.finished.emit()


class Window(QMainWindow):

    updateSignal = pyqtSignal(str)

    def __init__(self, parent=None):
        super().__init__(parent)
        self.setupUi()

    def setupUi(self):
        self.setWindowTitle("GUI")
        self.resize(300, 150)
        self.button2runThread = QPushButton('Click to start the thread', self)
        self.button2runThread.clicked.connect(self.runThread)

        self.button2updateParams = QPushButton('Click to update parameter in the running thread', self)
        self.button2updateParams.clicked.connect(self.updateThread)

        layout = QVBoxLayout()
        layout.addWidget(self.button2runThread)
        layout.addWidget(self.button2updateParams)
        
        self.centralWidget = QWidget()
        self.setCentralWidget(self.centralWidget)
        self.centralWidget.setLayout(layout)

    
    def updateThread(self):
        self.updateSignal.emit('My new string')
    def runThread(self):
        self.thread = QThread()
        self.worker = Worker()

        self.updateSignal.connect(self.worker.update)

        self.worker.moveToThread(self.thread)
        self.thread.started.connect(self.worker.run)
        self.worker.finished.connect(self.thread.quit)
        self.worker.finished.connect(self.worker.deleteLater)
        self.thread.finished.connect(self.thread.deleteLater)
        
        

        self.thread.start()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    win = Window()
    win.show()
    sys.exit(app.exec())
ocha67
  • 21
  • 4
  • See [this related answer](https://stackoverflow.com/a/20818401). Note that you could also consider using a QThread subclass and a simple Queue. It all depends on your implementation, and you have to carefully decide how to connect signals, considering that the target object may belong to another thread, and a QThread might need its event loop in order to properly process signals and events. – musicamante Jun 15 '22 at 14:53