'What should be the parent of a PyQt signal?

I have a PyQt GUI application with a main window and several workers of different types. I want to display notifications from the workers in the main window, something like:

class MainWindow(QMainWindow):
    messageToDisplaySignal = pyqtSignal(str)
    ...

    def displayStatus(self, s):
            self.infoLabel.setText(s)

class Worker1(QThread):
    ...
    def run(self):
        self.parent.messageToDisplaySignal.emit('Worker1 started')
        ...

I saw recommendations to send only signals created by the object itself, but this is violated here. If I create messageToDisplaySignal in Worker1, I will also need to implement similar signals in all worker classes and to subscribe to them all in MainWindow. Is there a more proper way to go?

What is the exact reason of these recommendations (to send only signals created by the object itself) is another interesting question. What exactly will be bad otherwise?



Solution 1:[1]

The only thing that matters is which specific thread the slot is called in. Qt does not support GUI-related operations of any kind outside the main thread, so it's not merely matter of "recommendations". Your displayStatus slot updates a label, which is not guaranteed to be a thread-safe operation. If you want to confirm that it's being called safely, some basic debugging can easily achieve that:

from PyQt5.QtCore import *
from PyQt5.QtWidgets import *

class MainWindow(QMainWindow):
    messageToDisplaySignal = pyqtSignal(str)

    def __init__(self):
        super().__init__()
        self.infoLabel = QLabel(self)
        self.setCentralWidget(self.infoLabel)
        self.messageToDisplaySignal.connect(self.displayStatus)
        self.worker = Worker1(self)
        self.worker.start()

    def displayStatus(self, s):
        print(f'slot: {int(QThread.currentThreadId())}')
        self.infoLabel.setText(s)

class Worker1(QThread):
    def run(self):
        print(f'worker: {int(QThread.currentThreadId())}')
        self.sleep(2)
        self.parent().messageToDisplaySignal.emit('Worker1 started')

print(f'main: {int(QThread.currentThreadId())}')
app = QApplication(['Test'])
window = MainWindow()
window.show()
app.exec_()

Output:

main: 140526289762112
worker: 140526172448320
slot: 140526289762112

So the slot is called in the main thread, which is what you want to see. The reason why this works is that Qt automatically detects which thread the signal is emitted from at runtime. If the sender and receiver are in different threads, the signal will be posted as an event to the event-queue of the receiving thread. When control returns to the receiving thread, the signal-event will be processed, and any connected slots will be called at that time.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1