ocha67
ocha67

Reputation: 21

Emitting signal to a thread

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())

Upvotes: 1

Views: 48

Answers (1)

ocha67
ocha67

Reputation: 21

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())

Upvotes: 1

Related Questions