Home > database >  PyQt delaying function's computation without blocking main thread
PyQt delaying function's computation without blocking main thread

Time:06-17

I've inhereted a GUI code which is structured something like this: any button signal triggers a slot, those slots then call an external process to receive information and wait until that process finishes, then the slot proceeds. the issue is, this external process takes between 0.5 to 60 seconds, and in that time the GUI freezes. i'm struggling to find a good way to seperate this process call to a different thread or QProcess (that way i will not block the main event loop) and then return and continue the relevent slot (or function) from that same point with the information received from that external slow process. generators seem like something that should go here, but im struggling to figure how to restructure the code so this will work. any suggestions or ideas? is there a Qt way to "yield" a function until that process completes and then continue that function?

Psudo code of the current structure:

    button1.clicked.connect(slot1)
    button2.clicked.connect(slot2)
    
    def slot1():
        status = subprocess.run("external proc") # this is blocking
        ...
        ...
        return

    def slot2():
        status = subprocess.run("external proc") # this is blocking
        ...
        ...
        return


CodePudding user response:

Here is the code with the example I was mentioning in the comments:

class MainWindow(QMainWindow, ui_MainWindow):
    def __init__(self):
        QtWidgets.QMainWindow.__init__(self)
        ui_MainWindow.__init__(self)
        self.setupUi(self)
    
        self.button_1.clicked.connect(lambda: self.threaded_wait(1))
        self.button_5.clicked.connect(lambda: self.threaded_wait(5))
        self.button_10.clicked.connect(lambda: self.threaded_wait(10))
    
        #Start a timer that executes every 0.5 seconds
        self.timer = QtCore.QBasicTimer()                                               
        self.timer.start(500, self)
    
        #INIT variables
        self.results = {}
        self.done = False
   
    def timerEvent(self, event):
        #Executes every 500msec.
        if self.done:
            print(self.results)
            self.done = False
    
   
    def threaded_wait(self, time_to_wait):
        self.done = False
        new_thread = threading.Thread(target=self.actual_wait, args=(time_to_wait,self.sender().objectName()))
        new_thread.start()
    
    def actual_wait(self, time_to_wait: int, button_name):
        print(f"Button {button_name} Pressed:\nSleeping for {int(time_to_wait)} seconds")

        time_passed = 0
    
        for i in range(0, time_to_wait):
            print(int( time_to_wait - time_passed))
            time.sleep(1)
            time_passed = time_passed   1
    
        self.results[button_name] = [1,2,3,4,5]
        self.done = True
        print("Done!")

enter image description here

CodePudding user response:

You can use QThread. With Qthread you can pass arguments to a function in mainWindow with signal mechanism.

Here is a source that explains how to use Qthread:

enter image description here

EDIT:

Sorry as for the second part of your question, if you want to wait for the thread to finish before doing something else, you can use a flag like this:

def actual_wait(self, time_to_wait: int):
    print(f"Sleeping for {int(time_to_wait)} seconds")

    ....
    
    self.DONE = True

And check that self.DONE flag wherever you need it. It kind of depends what you mean by wait for it to complete. I think if you use QThread you can also emit a signal when the thread is done and connect that signal to whatever slot after that, but I haven't used QThread.

  • Related