简体   繁体   中英

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?

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.

Check next code, maybe it can help you:

from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtCore    import QObject, QThread
from PyQt5           import Qt               #+


class WorkThread(Qt.QThread):

    threadSignal = Qt.pyqtSignal(int)  

    def __init__(self): 
        super().__init__()

    def run(self, *args, **kwargs):
        c = 0 
        while True:
            Qt.QThread.msleep(100)                    
            c += 1                                    
            self.threadSignal.emit(c)                 


class MsgBox(Qt.QDialog):
    def __init__(self):
        super().__init__()

        layout     = Qt.QVBoxLayout(self)
        self.label = Qt.QLabel("")
        layout.addWidget(self.label)
        close_btn  = Qt.QPushButton("Close")
        layout.addWidget(close_btn)

        close_btn.clicked.connect(self.close) 

        self.setGeometry(900, 65, 400, 80)
        self.setWindowTitle('MsgBox from WorkThread')        


class GUI(Qt.QWidget):     #(QMainWindow):

    def __init__(self):
        super().__init__()

        layout   = Qt.QVBoxLayout(self)
        self.btn = Qt.QPushButton("Start thread.")
        layout.addWidget(self.btn)
        self.btn.clicked.connect(self.startExecuting)

        self.msg    = MsgBox()  
        self.thread = None
        # Lots of irrelevant code here ...

    # Called when "Start/Stop Executing" button is pressed
    def startExecuting(self, user_script):

        if self.thread is None:                     
            self.thread = WorkThread()

            self.thread.threadSignal.connect(self.on_threadSignal)
            self.thread.start()                     

            self.btn.setText("Stop thread")         
        else:
            self.thread.terminate()         
            self.thread = None
            self.btn.setText("Start thread")


    def on_threadSignal(self, value):
        self.msg.label.setText(str(value))
        if not self.msg.isVisible():        
            self.msg.show()        


if __name__ == '__main__':
    app = Qt.QApplication([])
    mw  = GUI()
    mw.show()
    app.exec()   

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM