Search code examples
pythonpyqtpyqt5qthreadqobject

Setup signal and slot before moving Worker object to QThread in pyqt


In Qt/PyQt, I used to make threading using a Worker class and a QThread.

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

worker.moveToThread(thread)
setup_signal_slot_with_main_object()

// start 
thread.start()

I must place setup_signal_slot_with_main_object() after moveToThread(). But I have a complex worker. In Worker.__ init__(), it creates many QObjects and connects internal signals and slots. I don't want to create a method which makes all connections and calls worker.setup_signal_slot() after worker->moveToThread(&thread) because Worker contains many child QObjects and each QObject can make signal/slot in its constructor.

In Qt/C++, I can make signal/slot connection in worker's constructor. But in PyQt, slot will not run in new thread.

This is an example with a Worker contains a QTimer

import sys
import signal
import threading
from PyQt5.QtCore import QObject, pyqtSignal, QTimer, QCoreApplication, QThread
import datetime


class Worker(QObject):
    timeChanged = pyqtSignal(object)

    def __init__(self, parent=None):
        QObject.__init__(self, parent)

        self.timer = QTimer(self)
        self.timer.setInterval(1000)

        # I want to make connection at here
        self.timer.timeout.connect(self.main_process)

    def start(self):
        # self.timer.timeout.connect(self.main_process)
        self.timer.start()
        print('Worker thread {}: Start timer'.format(threading.get_ident()))

    # this method still run in main thread
    def main_process(self):
        timestamp = datetime.datetime.now()
        print('Worker thread {}: {}'.format(threading.get_ident(), timestamp.strftime('%d-%m-%Y %H-%M-%S')))
        self.timeChanged.emit(timestamp)


class WorkerThread(QObject):
    def __init__(self, parent=None):
        QObject.__init__(self, parent)

        self.emitter = Worker()
        self.thread = QThread(self)
        self.emitter.moveToThread(self.thread)

        self.thread.started.connect(self.emitter.start)
        self.thread.finished.connect(self.emitter.deleteLater)
        self.emitter.timeChanged.connect(self.show_time)

    def start(self):
        self.thread.start()

    def stop(self):
        if self.thread.isRunning():
            self.thread.quit()
            self.thread.wait()
            print('Exit thread')

    def show_time(self, timestamp):
        print('Main   thread {}: {}'.format(threading.get_ident(), timestamp.strftime('%d-%m-%Y %H-%M-%S')))


def signal_handler(sig, frame):
    print('Quit')
    app.quit()


if __name__ == '__main__':
    signal.signal(signal.SIGINT, signal_handler)
    signal.signal(signal.SIGTERM, signal_handler)

    app = QCoreApplication(sys.argv)

    timer = QTimer()
    timer.timeout.connect(lambda: None)
    timer.start(500)

    print('Main    thread {}'.format(threading.get_ident()))
    emitter = WorkerThread()
    emitter.start()
    sys.exit(app.exec_())

In Worker, timer timeout will call main_process in main thread. I can move self.timer.timeout.connect(self.main_process) into method worker.start(). But as I have said above, I still want to place internal signal/slot in its constructor. Could anyone suggest me a solution ? Thanks !


Solution

  • If you want the methods to be invoked in the same thread where the receiver uses the pyqtSlot() decorator, if you don't do it then it will be called in the sender's thread.

    import sys
    import signal
    import threading
    import datetime
    
    from PyQt5.QtCore import QObject, pyqtSignal, QTimer, QCoreApplication, QThread, pyqtSlot
    
    
    class Worker(QObject):
        timeChanged = pyqtSignal(object)
    
        def __init__(self, parent=None):
            QObject.__init__(self, parent)
    
            self.timer = QTimer(self)
            self.timer.setInterval(1000)
    
            self.timer.timeout.connect(self.main_process)
    
        @pyqtSlot()
        def start(self):
            self.timer.start()
            print("Worker thread {}: Start timer".format(threading.get_ident()))
    
        @pyqtSlot()
        def main_process(self):
            timestamp = datetime.datetime.now()
            print(
                "Worker thread {}: {}".format(
                    threading.get_ident(), timestamp.strftime("%d-%m-%Y %H-%M-%S")
                )
            )
            self.timeChanged.emit(timestamp)
    
    
    class WorkerThread(QObject):
        def __init__(self, parent=None):
            QObject.__init__(self, parent)
    
            self.emitter = Worker()
            self.thread = QThread(self)
            self.emitter.moveToThread(self.thread)
    
            self.thread.started.connect(self.emitter.start)
            self.thread.finished.connect(self.emitter.deleteLater)
            self.emitter.timeChanged.connect(self.show_time)
    
        @pyqtSlot()
        def start(self):
            self.thread.start()
    
        def stop(self):
            if self.thread.isRunning():
                self.thread.quit()
                self.thread.wait()
                print("Exit thread")
    
        @pyqtSlot(object)
        def show_time(self, timestamp):
            print(
                "Main   thread {}: {}".format(
                    threading.get_ident(), timestamp.strftime("%d-%m-%Y %H-%M-%S")
                )
            )
    
    
    def signal_handler(sig, frame):
        print("Quit")
        app.quit()
    
    
    if __name__ == "__main__":
        signal.signal(signal.SIGINT, signal_handler)
        signal.signal(signal.SIGTERM, signal_handler)
    
        app = QCoreApplication(sys.argv)
    
        timer = QTimer()
        timer.timeout.connect(lambda: None)
        timer.start(500)
    
        print("Main    thread {}".format(threading.get_ident()))
        emitter = WorkerThread()
        emitter.start()
        sys.exit(app.exec_())
    

    Output:

    Main    thread 140175719339648
    Worker thread 140175659480832: Start timer
    Worker thread 140175659480832: 26-07-2019 04-39-42
    Main   thread 140175719339648: 26-07-2019 04-39-42
    Worker thread 140175659480832: 26-07-2019 04-39-43
    Main   thread 140175719339648: 26-07-2019 04-39-43
    Worker thread 140175659480832: 26-07-2019 04-39-44
    Main   thread 140175719339648: 26-07-2019 04-39-44
    Worker thread 140175659480832: 26-07-2019 04-39-45
    Main   thread 140175719339648: 26-07-2019 04-39-45