Search code examples
pythonpython-3.xpyqt5qthreadpyside2

Python QThread in GUI


I'm trying to follow this guide to let a long-running method run in a separate thread. The functionality is working, but it is still causing the GUI to freeze while the method is running. Am I missing something that would let this run in a separate thread?

from PySide2.QtWidgets import QDialog, QApplication, QMainWindow
from PySide2.QtCore import Qt, QThread, SIGNAL
import time

class MyClient():
    '''Members of this class get passed to the QThread subclass instance in order to "doSomething" in a separate thread'''
    def __init__(self, name):
        self.name = name

    def doSomething(self):
        time.sleep(10) # but really do something more useful
        return self.name

class WorkerThread(QThread):
    '''Supposed to perform operation in a separate thread so GUI remains responsive.'''

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

    def __del__(self):
        self.wait()

    def run(self):
        print("Running!!!")
        return self.client.doSomething()

class MyForm(QMainWindow):
    def __init__(self, clients):
        super().__init__()
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        self.clients = clients

        # Connect button to method
        self.ui.btn_fetch.clicked.connect(self.fetch)

        self.show()
        self.fetch()

    def printName(self, name):
        print(name)

    def fetch(self):
        for client in self.clients:
            self.workerThread = WorkerThread(client)

            self.connect(self.workerThread, SIGNAL("printName(QString)"), self.printName)

            print("Starting thread")
            self.workerThread.start()
            # GUI becomes unresponsive here until the workerThread finishes.
            print("Thread started")

if __name__ == "__main__":

    clients = [MyClient('foo'), MyClient('bar'), MyClient('baz')]

    app = QApplication(sys.argv)
    w = MyForm(clients)
    w.show()
    sys.exit(app.exec_())

Solution

  • Here is a working example using PyQt5 / PySide2 signals:

    import sys
    import time
    
    from PyQt5.QtWidgets import QDialog, QApplication, QMainWindow
    from PyQt5.QtCore import Qt, QThread, QObject, pyqtSignal
    
    # If you need Pyside2 like in your example (untested):
    # from PySide2.QtWidgets import QDialog, QApplication, QMainWindow
    # from PySide2.QtCore import Qt, QThread, QObject, Signal
    
    
    class MyClient:
        """Members of this class get passed to the QThread subclass instance
        in order to "doSomething" in a separate thread"""
        def __init__(self, name):
            self.name = name
    
        def doSomething(self):
            time.sleep(10)  # but really do something more useful
            return self.name
    
    
    class WorkerThread(QThread):
        didSomething = pyqtSignal(str)
    
        """Supposed to perform operation in a separate thread so GUI remains responsive."""
        def __init__(self, client):
            super().__init__()
            self.client = client
    
        def run(self):
            print("Running!!!")
            self.didSomething.emit(self.client.doSomething())
    
    
    class MyForm(QMainWindow):
        def __init__(self, clients):
            super().__init__()
            self.clients = clients
    
            # self.ui = Ui_MainWindow()
            # self.ui.setupUi(self)
    
            # Connect button to method
            # self.ui.btn_fetch.clicked.connect(self.fetch)
    
            self.workerThreads = []
    
            self.fetch()
    
        def printName(self, name):
            print(name)
    
        def removeThread(self, workerThread):
            def removeWorkerThread():
                self.workerThreads.remove(workerThread)
                print('Thread removed. Total active threads: {}'.format(len(self.workerThreads)))
            return removeWorkerThread
    
        def fetch(self):
            for client in self.clients:
                workerThread = WorkerThread(client)
                workerThread.didSomething.connect(self.printName)
                workerThread.finished.connect(self.removeThread(workerThread))
                self.workerThreads.append(workerThread)
    
                print("Starting thread")
                workerThread.start()
    
                # GUI becomes unresponsive here until the workerThread finishes.
                print("Thread started")
    
    
    if __name__ == "__main__":
        myClients = [MyClient('foo'), MyClient('bar'), MyClient('baz')]
    
        app = QApplication(sys.argv)
        w = MyForm(myClients)
        w.show()
        sys.exit(app.exec_())