Search code examples
pythonpython-3.xpyqtpyqt5qthread

How to interrupt a script execution on a QThread in Python PyQt?


What I'm Doing:

I'm making a PyQt app that allows users to select a script file from their machine, the app then executes it using exec() on a separate QThread then shows them the results. I've already implemented all that, and now I'm trying to add a "Stop Executing" button.

The Problem:

I'm not able to interrupt the script execution, which should happen whenever the user presses the "Stop Executing" button. I can't stop the QObject's task that's executing the script or terminate the QThread that's hosting the object.

My Code:

from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtCore import QObject, QThread

class Execute(QObject):
    def __init__(self, script):
        super().__init__()
        self.script = script

    def run(self):
        exec(open(self.script).read())

class GUI(QMainWindow):
    # Lots of irrelevant code here ...

    # Called when "Start Executing" button is pressed
    def startExecuting(self, user_script):
        self.thread = QThread()
        self.test = Execute(user_script)
        self.test.moveToThread(self.thread)
        self.thread.started.connect(self.test.run)
        self.thread.start()

    # Called when "Stop Executing" button is pressed
    def stopExecuting(self):
        # Somehow stop script execution

My Attempts:

There's a ton of question related to stopping an exec() or a QThread, but none of them work in my case. Here's what I've tried:

  1. Calling thread.quit() from GUI (kills thread after script execution ends - same with wait())
  2. Raising a SystemExit from object (exits the whole app after script execution ends)
  3. Calling thread.terminate() from GUI (app crashes when "Stop Executing" button is pressed)
  4. Using a termination flag variable (not applicable in my case as run() isn't loop based)

So, is there any other solution to stop the exec() or kill the thread right when the button is pressed?


Solution

  • Thank's to @ekhumoro's hint about using multiprocessing instead of multithreading, I was able to find a solution.

    I used a QProcess to execute the script, and then called process.kill() when the "Stop Executing" button is clicked. Like so:

    from PyQt5.QtWidgets import QMainWindow
    from PyQt5.QtCore import QProcess
    
    class GUI(QMainWindow):
        # Lots of irrelevant code here ...
    
        # Called when "Start Executing" button is pressed
        def startExecuting(self, user_script):
            self.process = QProcess()
            self.process.setProcessChannelMode(QProcess.MergedChannels)
            self.process.start("python", ["-u", user_script])
    
        # Called when "Stop Executing" button is pressed
        def stopExecuting(self):
            self.process.kill()
    

    This stops the script execution right away without interrupting the GUI process, which's exactly what I was looking for.