Search code examples
pythonpyqtsignals-slotsqthread

QThread.finished signal isn't emitted after worker finishes


I have the following code:

import time

from PyQt5.QtCore import QThread, QObject
from PyQt5.QtWidgets import QWidget, QApplication


class Worker(QObject):

    def run(self):
        time.sleep(1)
        print("Worker is finished")


class MainWindow(QWidget):

    def __init__(self):
        super().__init__()
        self.show()

        self.thread = QThread()
        self.worker = Worker()
        self.worker.moveToThread(self.thread)
        self.thread.started.connect(self.worker.run)
        self.thread.finished.connect(self.on_thread_finished)
        self.thread.start()

    def on_thread_finished(self):
        print("Thread finished")
        self.thread.deleteLater()


if __name__ == "__main__":
    app = QApplication([])
    window = MainWindow()
    app.exec_()

Run it, it shows the window, prints Worker is finished, nothing more. That's weird. IMHO when the worker is finished, the thread should be finished too, which means the on_thread_finished method should be called and Thread finished should be printed. But it wasn't. Why?


Solution

  • when the worker is finished, the thread should be finished too

    That's not how it works. Your Worker::run method is being invoked as a slot via the usual signal/slot mechanism after which the QThread will continue to process events as normal.

    If you want to terminate the QThread when Worker::run has completed you need to tell it to do so explicitly...

    import time
    
    from PyQt5.QtCore import Qt, QThread, QObject
    from PyQt5.QtWidgets import QWidget, QApplication
    
    
    class Worker(QObject):
    
        # Constructor accepts the QThread as a parameter and stashes it
        # for later use.
        def __init__(self, thread):
            super(Worker, self).__init__()
            self.m_thread = thread
        def run(self):
            time.sleep(1)
            print("Worker is finished")
    
            # We're done so ask the `QThread` to terminate.
            if self.m_thread:
                self.m_thread.quit()
    
    
    class MainWindow(QWidget):
    
        def __init__(self):
            super().__init__()
            self.show()
    
            self.thread = QThread()
    
            # Pass the QThread to the Worker's ctor.
            self.worker = Worker(self.thread)
            self.worker.moveToThread(self.thread)
            self.thread.started.connect(self.worker.run)
            self.thread.finished.connect(self.on_thread_finished)
            self.thread.start()
    
        def on_thread_finished(self):
            print("Thread finished")
            self.thread.deleteLater()
    
    
    if __name__ == "__main__":
        app = QApplication([])
        window = MainWindow()
        app.exec_()
    

    Very 'inelegant' but it conveys the idea.

    A simpler alternative that springs to mind would be to simply make use of QThread::currentThread(), in which case your Worker::run method becomes...

    class Worker(QObject):
        def run(self):
            time.sleep(1)
            print("Worker is finished")
    
            # We're done so ask the `QThread` to terminate.
            QThread.currentThread().quit()