Search code examples
pythonpyqtpyqt5qthread

PyQt QThread: How to run a function on a worker object in the same thread?


I just can't figure out how to use QThreads properly. I have a worker object, and a gui here that has two combo boxes: when a user chooses an item on the comboWorkspace, the items on the comboModel will load. What's happening is that whenever I choose from comboWorkspace, it will run the get_workspaces function from the worker again. This is probably because I'm calling the "start" function of the QThread over and over again. I'm so sorry if there are a lot of things wrong here. If anyone can just give ma an idea on how to approach this properly. Thank you so much!

*the full code with the setupUi function is in here : pastebin.com/Y7dGbRLu

from PyQt5 import QtCore, QtGui, QtWidgets
import os
import time
from functools import partial
import copy

class anaplanWorker(QtCore.QObject):

    signal_workspacenames = QtCore.pyqtSignal(str)
    signal_modelnames = QtCore.pyqtSignal(str)
    finish_progressbar = QtCore.pyqtSignal()
    start_progressbar = QtCore.pyqtSignal()
    finish_workspace_thread = QtCore.pyqtSignal()
    finish_model_thread = QtCore.pyqtSignal()
    count_run = 0

    def __init__(self, parent=None):
         QtCore.QThread.__init__(self, parent)


    def get_workspaces(self):
        ws_names = ['Name One', 'Name Two', 'Name Three']

        self.start_progressbar.emit()
        for ws_name in ws_names:
            self.signal_workspacenames.emit(ws_name)

        self.finish_workspace_thread.emit()


    def get_models(self,workspaceindex):

        self.start_progressbar.emit()

        models = ['Model One', 'Model Two', 'Model Three']

        for model_name in models:
            self.signal_modelnames.emit(model_name)

        self.finish_model_thread.emit()


class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        """ui setup for combo combo boxes: comboWorkspace and comboModel"""

    def progress_start(self):
        print("start")

    def progress_finish(self):
        print("finish")

#workspace functions
    def add_workspace(self, workspace_name):
        self.comboWorkspace.addItem(workspace_name)

    def start_workspace(self):

        self.comboWorkspace.clear()
        self.workspace_thread = self.anaplan_thread
        self.anaplan_worker.moveToThread(self.workspace_thread)
        self.workspace_thread.started.connect(self.anaplan_worker.get_workspaces)        
        self.workspace_thread.start()

#model functions
    def add_model(self, model_name):
        self.comboModel.addItem(model_name)

    def start_model(self):
        #clear combomodel
        self.comboModel.clear()
        workspaceIndex = self.comboWorkspace.currentIndex()

        self.model_thread = self.anaplan_thread
        self.anaplan_worker.moveToThread(self.model_thread)

        self.model_thread.started.connect(partial(self.anaplan_worker.get_models, workspaceIndex))

        self.model_thread.start()

#quit threads
    def quit_model_thread(self):
        self.model_thread.quit()
        print("quit model thread")

    def quit_workspace_thread(self):
        self.workspace_thread.quit()
        print("quit workspace thread")


    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "Tool"))
        self.labelWorkspace.setText(_translate("MainWindow", "Workspace"))
        self.labelModel.setText(_translate("MainWindow", "Model"))

        self.anaplan_worker = anaplanWorker()
        self.anaplan_thread = QtCore.QThread()

        self.anaplan_worker.signal_workspacenames.connect(self.add_workspace)

        self.anaplan_worker.signal_modelnames.connect(self.add_model)

        self.anaplan_worker.finish_workspace_thread.connect(self.quit_workspace_thread)

        self.anaplan_worker.finish_model_thread.connect(self.quit_model_thread)

        self.comboWorkspace.activated[str].connect(self.start_model)


if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    MainWindow = QtWidgets.QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(MainWindow)
    MainWindow.show()
    ui.start_workspace()
    sys.exit(app.exec_())

Solution

  • You should split your code more. It would fix a lot of your problem.

    You have two main problems: you are setting again the signals/slots connections each time you change the index (one connection means one call). You are using the same thread for two different tasks.

    You should create a new class MainWindow and move your business logic in it. Your Ui_MainWindow class should handle the UI part only (widgets, layouts, etc.).

    Your anaplanWorker has a bunch of problems: it ingerits from QObject but calls QThread.__init__ and you use it to create a thread and an object worker. It would be clearer to use directly QThread.

    The class should be splitted into two different parts, also: one for the workspace and one for the model:

    class ModelWorker(QtCore.QObject):
    
        signal_modelnames = QtCore.pyqtSignal(str)
        finish_progressbar = QtCore.pyqtSignal()
        start_progressbar = QtCore.pyqtSignal()
        finish_model_thread = QtCore.pyqtSignal()
    
        def __init__(self, parent=None):
             super().__init__(parent) 
    
        def get_models(self,workspaceindex):
            print("Get models with", workspaceindex)
            self.start_progressbar.emit()
    
            models = ['Model One', 'Model Two', 'Model Three']
    
            for model_name in models:
                self.signal_modelnames.emit(model_name)
    
            self.finish_model_thread.emit()
    
    class WorkspaceWorker(QtCore.QObject):
        signal_workspacenames = QtCore.pyqtSignal(str)
        finish_progressbar = QtCore.pyqtSignal()
        start_progressbar = QtCore.pyqtSignal()
        finish_workspace_thread = QtCore.pyqtSignal()
        finish_model_thread = QtCore.pyqtSignal()
    
        def __init__(self, parent=None):
             super().__init__(parent)
    
    
        def get_workspaces(self):
            ws_names = ['Name One', 'Name Two', 'Name Three']
    
            self.start_progressbar.emit()
            for ws_name in ws_names:
                self.signal_workspacenames.emit(ws_name)
    
            self.finish_workspace_thread.emit()
    

    Then, in your MainWindow class, create and setup the threads and the connections. You just have to start the thread when needed.

    class MainWindow(QMainWindow, Ui_MainWindow):
        def __init__(self, parent=None):
            super().__init__(parent)
    
            self.setupUi(self)
    
            self.workspaceWorker = WorkspaceWorker()
            self.modelWorker = ModelWorker()
    
            self.workspaceWorker_thread = QThread(self)
            self.modelWorker_thread = QThread(self)
    
            self.workspaceWorker.moveToThread(self.workspaceWorker_thread)
            self.modelWorker.moveToThread(self.modelWorker_thread)
    
            self.workspaceWorker.signal_workspacenames.connect(self.add_workspace)
            self.workspaceWorker.finish_workspace_thread.connect(self.quit_workspace_thread)
    
            self.modelWorker.signal_modelnames.connect(self.add_model)
            self.modelWorker.finish_model_thread.connect(self.quit_model_thread)
    
            self.comboWorkspace.activated[str].connect(self.start_model)
    
            self.modelWorker_thread.started.connect(lambda: self.modelWorker.get_models(self.comboWorkspace.currentIndex()))
            self.workspaceWorker_thread.started.connect(self.workspaceWorker.get_workspaces)
    
            self.start_workspace()
    
    
        def add_workspace(self, workspace_name):
            self.comboWorkspace.addItem(workspace_name)
    
        def start_workspace(self):
    
            self.comboWorkspace.clear()
            self.workspaceWorker_thread.start()
    
    #model functions
        def add_model(self, model_name):
            self.comboModel.addItem(model_name)
    
        def start_model(self):
            #clear combomodel
            self.comboModel.clear()
            self.modelWorker_thread.start()
    
    #quit threads
        def quit_model_thread(self):
            self.modelWorker_thread.quit()
            print("quit model thread")
    
        def quit_workspace_thread(self):
            self.workspaceWorker_thread.quit()
            print("quit workspace thread")