Search code examples
pythonmultithreadingpyqtqthread

QThread Windows not responding


I wrote a GUI program in PyQt on Windows. There's some expensive operations in my program. While these operations are running, the program shows "Not Responding" in the program bar.

I think it must be this operation block the main thread to update the UI, so I write multi-threading code by QThread to test it, it still not make sense.

I wrote a small program to test it, the operation did not run in new thread at all, here is my small test code:

from PyQt5.QtCore import QThread, QObject, QCoreApplication, qDebug, QTimer


class Worker(QObject):
    def on_timeout(self):
        qDebug('Worker.on_timeout get called from: %s' % hex(int(QThread.currentThreadId())))


if __name__ == '__main__':
    import sys

    app = QCoreApplication(sys.argv)
    qDebug('From main thread: %s' % hex(int(QThread.currentThreadId())))
    t = QThread()
    qDebug(t)
    worker = Worker()
    timer = QTimer()
    timer.timeout.connect(worker.on_timeout)
    timer.start(1000)
    timer.moveToThread(t)
    worker.moveToThread(t)
    t.start()

    app.exec_()

Here is the output:

From main thread: 0x634
Worker.on_timeout get called from: 0x634

Solution

  • Your program has several errors and does not produce the output that you show.

    Firstly, it is not possible to pass a thread object to qDebug - the argument must be a string. If you want to print objects, use qDebug(repr(obj)) - or even better, just use print(obj).

    Secondly, you cannot start a timer outside of the thread that created it. Your example makes the signal connection in the main thread, and starts the timer in the main thread as well. So worker.on_timeout will be called in the main thread. But if you connect and start the timer after moving it to the worker thread, you will get this error:

    QObject::startTimer: Timers can only be used with threads started with QThread

    I think using a timer is unnecessary, and confuses your example, so it is better to leave it out altogether. Instead, you should connect the started signal of the worker thread to a run method of the worker object. To simulate a long-running operation, you can use QThread.sleep():

    from PyQt5.QtCore import QThread, QObject, QCoreApplication
    
    class Worker(QObject):
        def run(self):
            print('Worker called from: %#x' % int(QThread.currentThreadId()))
            QThread.sleep(2)
            print('Finished')
            QCoreApplication.quit()
    
    if __name__ == '__main__':
    
        import sys
        app = QCoreApplication(sys.argv)
        print('From main thread: %#x' % int(QThread.currentThreadId()))
        t = QThread()
        worker = Worker()
        worker.moveToThread(t)
        t.started.connect(worker.run)
        t.start()
        app.exec_()
    

    Finally, note that you should always make signal connections after moving a worker object to a thread. The reasons for this are explained in this answer.