Search code examples
pythonmultithreadingpyqt5signals-slotsqthread

PyQt – Signal for object created in different thread doesn't work


I’m implementing multithreading in PyQt5 and Python 3.5 by running a Worker inside a QThread. In the following sample thread2_worker (runs inside a secondary thread and) creates thread3_worker, connects thread3_worker.finished signal to thread3_worker_finished() and runs it.

When thread3_worker is done it emits finished from within its thread but the connection doesn’t work. My guess is that it has to do with thread3_worker being created or connected not in the main thread but I'd welcome any clarification.

import time
from PyQt5.QtCore import QObject, QThread, pyqtSignal, pyqtSlot, QCoreApplication


class Worker(QObject):
    # Generic worker.
    finished = pyqtSignal()

    def __init__(self, func):
        super().__init__()
        self.func = func

    def run(self):
        self.func()
        self.finished.emit()


def thread_factory(func):
    # Creates a Worker, a QThread and moves the Worker inside the QThread.
    worker = Worker(func)
    thread = QThread()
    worker.moveToThread(thread)
    thread.started.connect(worker.run)

    # Provision graceful termination.
    worker.finished.connect(thread.quit)
    worker.finished.connect(worker.deleteLater)
    thread.finished.connect(thread.deleteLater)

    return worker, thread


def wait():
    print("thread3:\t{}".format(QThread.currentThread()))
    time.sleep(3)


# finished signal of thread3_worker is never received.
@pyqtSlot()
def thread3_worker_finished():
    QCoreApplication.exit()


def create_thread3():
    print("thread2:\t{}".format(QThread.currentThread()))
    global thread3_worker, thread3
    # thread3_worker runs inside thread3, and all it does is call wait().
    thread3_worker, thread3 = thread_factory(wait)
    thread3_worker.finished.connect(thread3_worker_finished)  # FIXME Doesn't work.
    thread3.start()


app = QCoreApplication([])
print("Main thread:\t{}".format(QThread.currentThread()))
thread3_worker, thread3 = None, None
# thread2_worker runs inside thread2, and creates and runs thread3_worker.
thread2_worker, thread2 = thread_factory(create_thread3)
thread2.start()
app.exec_()

Solution

  • Cross-thread signals require an event-loop. Your thread_factory function connects the finished signal of the worker to the quit slot of its thread. The quit slot asks the thread to exit its event-loop.

    So after thread3 starts, worker2 finishes and thread2 quits. Then when the finished signal of worker3 is emitted, there is no longer an event-loop running that can process it. If you comment out the line worker.finished.connect(thread.quit), your example should work.