如何从 GUI 停止 QThread

Posted

技术标签:

【中文标题】如何从 GUI 停止 QThread【英文标题】:How to stop a QThread from the GUI 【发布时间】:2013-04-21 05:44:27 【问题描述】:

这是我之前发布的上一个问题的后续问题。 问题是如何在使用不继承 Qthread 的推荐方法,而是创建 QObject 然后将其移动到 QThread 时从 GUI 停止(终止|退出|退出)QThread。下面是一个工作示例。我可以启动 GUI 和 Qthread,我可以让后者更新 GUI。但是,我无法阻止它。我为 qthread 尝试了几种方法(quit()、exit() 甚至 terminate()),但均无济于事。 非常感谢帮助。

完整代码如下:

import time, sys
from PyQt4.QtCore  import *
from PyQt4.QtGui import * 

class SimulRunner(QObject):
    'Object managing the simulation'

    stepIncreased = pyqtSignal(int, name = 'stepIncreased')
    def __init__(self):
        super(SimulRunner, self).__init__()
        self._step = 0
        self._isRunning = True
        self._maxSteps = 20

    def longRunning(self):
        while self._step  < self._maxSteps  and self._isRunning == True:
            self._step += 1
            self.stepIncreased.emit(self._step)
            time.sleep(0.1)

    def stop(self):
        self._isRunning = False

class SimulationUi(QDialog):
    'PyQt interface'

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

        self.goButton = QPushButton('Go')
        self.stopButton = QPushButton('Stop')
        self.currentStep = QSpinBox()

        self.layout = QHBoxLayout()
        self.layout.addWidget(self.goButton)
        self.layout.addWidget(self.stopButton)
        self.layout.addWidget(self.currentStep)
        self.setLayout(self.layout)

        self.simulRunner = SimulRunner()
        self.simulThread = QThread()
        self.simulRunner.moveToThread(self.simulThread)
        self.simulRunner.stepIncreased.connect(self.currentStep.setValue)


        self.stopButton.clicked.connect(simulThread.qui)  # also tried exit() and terminate()
        # also tried the following (didn't work)
        # self.stopButton.clicked.connect(self.simulRunner.stop)
        self.goButton.clicked.connect(self.simulThread.start)
        self.simulThread.started.connect(self.simulRunner.longRunning)
        self.simulRunner.stepIncreased.connect(self.current.step.setValue)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    simul = SimulationUi()
    simul.show()
    sys.exit(app.exec_())

【问题讨论】:

[qt-project.org/doc/qt-4.8/qthread.html#quit] 真的应该是去这里的方式......再试一次,也许围绕函数调用进行一些基本调试? 【参考方案1】:

我很久以前就知道了,但我只是偶然发现了同样的问题。

我也一直在寻找合适的方法来做到这一点。最后很容易。退出应用程序时,需要停止任务,并且需要停止调用其退出方法的线程。请参阅底部的 stop_thread 方法。你需要等待线程完成。否则,您将在退出时收到 QThread: Destroyed while thread is still running' 消息

(我也将代码更改为使用 pyside)

import time, sys
from PySide.QtCore  import *
from PySide.QtGui import *

class Worker(QObject):
    'Object managing the simulation'

    stepIncreased = Signal(int)

    def __init__(self):
        super(Worker, self).__init__()
        self._step = 0
        self._isRunning = True
        self._maxSteps = 20

    def task(self):
        if not self._isRunning:
            self._isRunning = True
            self._step = 0

        while self._step  < self._maxSteps  and self._isRunning == True:
            self._step += 1
            self.stepIncreased.emit(self._step)
            time.sleep(0.1)

        print "finished..."

    def stop(self):
        self._isRunning = False


class SimulationUi(QDialog):
    def __init__(self):
        super(SimulationUi, self).__init__()

        self.btnStart = QPushButton('Start')
        self.btnStop = QPushButton('Stop')
        self.currentStep = QSpinBox()

        self.layout = QHBoxLayout()
        self.layout.addWidget(self.btnStart)
        self.layout.addWidget(self.btnStop)
        self.layout.addWidget(self.currentStep)
        self.setLayout(self.layout)

        self.thread = QThread()
        self.thread.start()

        self.worker = Worker()
        self.worker.moveToThread(self.thread)
        self.worker.stepIncreased.connect(self.currentStep.setValue)

        self.btnStop.clicked.connect(lambda: self.worker.stop())
        self.btnStart.clicked.connect(self.worker.task)

        self.finished.connect(self.stop_thread)

    def stop_thread(self):
        self.worker.stop()
        self.thread.quit()
        self.thread.wait()

if __name__ == '__main__':
    app = QApplication(sys.argv)
    simul = SimulationUi()
    simul.show()
    sys.exit(app.exec_())

【讨论】:

轻微改进:使用requestInterruption() and isInterruptionRequested() 可能比您自己的自定义标志更好。【参考方案2】:

我发现我原来的问题实际上是两个问题合二为一:为了从主线程停止辅助线程,您需要两件事:

    能够从主线程向下到次线程通信

    发送适当的信号来停止线程

我无法解决 (2),但我想出了如何解决 (1),这为我提供了解决原始问题的方法。除了停止线程,我可以停止线程的处理longRunning() 方法)

问题是辅助线程只有在运行自己的事件循环时才能响应信号。常规的 Qthread(这是我的代码使用的)没有。不过,将 QThread 子类化到这个效果是很容易的:

class MyThread(QThread):
    def run(self):
        self.exec_()

并在我的代码中使用了self.simulThread = MyThread(),而不是原来的self.simulThread = Qthread()。 这可确保辅助线程运行事件循环。但这还不够。 longRunning() 方法需要有机会实际处理从主线程下来的事件。在this SO answer 的帮助下,我发现在longRunning() 方法中简单地添加QApplication.processEvent() 为辅助线程提供了这样的机会。我现在可以停止在辅助线程中执行的处理,即使我还没有弄清楚如何停止线程本身。

总结一下。我的 longRunning 方法现在看起来像这样:

def longRunning(self):
    while self._step  < self._maxSteps  and self._isRunning == True:
        self._step += 1
        self.stepIncreased.emit(self._step)
        time.sleep(0.1)
        QApplication.processEvents() 

我的 GUI 线程有这三行来完成这项工作(除了上面列出的 QThread 子类):

    self.simulThread = MyThread()
    self.simulRunner.moveToThread(self.simulThread)
    self.stopButton.clicked.connect(self.simulRunner.stop)

欢迎评论!

【讨论】:

我遇到了很多问题,然后创建了自己的解决方案,看看:github.com/u2ros/python-qt-multithreading【参考方案3】:

您可以通过调用 exit() 或 quit() 来停止线程。在极端情况下,您可能希望强制终止()正在执行的线程。然而,这样做是危险的,也是令人沮丧的。有关详细信息,请阅读 terminate() 和 setTerminationEnabled() 的文档。

src:https://doc.qt.io/qtforpython/PySide2/QtCore/QThread.html

【讨论】:

以上是关于如何从 GUI 停止 QThread的主要内容,如果未能解决你的问题,请参考以下文章

如何在不关闭 GUI 窗口的情况下停止运行 PyQt5 程序?

如何停止运行没有 GUI 窗口的 Python .pyw 脚本?

后台 iCloud 处理减慢 gui 和损坏视图控制器堆栈-如何停止多次触摸

为所有具有 GUI 会话的用户启动/停止启动代理

Visual C++/Cli 中的异步睡眠,如何在不使 GUI 停止的情况下创建一个 X 毫秒来调用函数

如何使用 Web GUI 停止 Google Cloud AutoML(现为 Vertex AI)批量预测作业?