如何从GUI停止一个QThread

21
这是对我之前发布的一个问题的跟进。问题是如何在不使用子类化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_())

[http://qt-project.org/doc/qt-4.8/qthread.html#quit] 真的应该是这里的正确方法...再试一次,也许在函数调用周围加上一些基本的调试代码? - tmpearce
3个回答

25

我知道这是很久以前的事了,但我刚刚遇到了同样的问题。

我一直在寻找一种合适的方法来解决这个问题。最终很简单。在退出应用程序时,需要停止任务并调用其quit方法停止线程。请参见底部的stop_thread方法。您还需要等待线程完成。否则,退出时会得到“QThread:销毁时仍在运行”的消息。

(我还改变了我的代码以使用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_())

4
改善建议:使用requestInterruption()和isInterruptionRequested(),而不是自定义标志位。 - midrare

6

我发现我的原始问题实际上包含两个问题:为了停止从主线程到次要线程,你需要两件事情:

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

  2. 发送适当的信号以停止线程

我还没有解决(2),但我找到了如何解决(1)的方法,这为我原来的问题提供了一个解决方法。我可以停止线程的处理(longRunning() 方法),而不是停止线程本身。

问题在于,只有运行自己的事件循环的次要线程才能响应信号。常规的 QThread(这是我的代码使用的)没有。不过,将 QThread 子类化以实现这一点非常容易:

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

我在我的代码中使用了self.simulThread = MyThread()代替原来的self.simulThread = Qthread(),以确保次要线程运行事件循环。但这还不够。 longRunning()方法需要有机会实际处理来自主线程的事件。通过这个SO答案的帮助,我发现在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)

欢迎留言评论!


我曾经遇到了很多问题,然后创建了自己的解决方案,请查看:https://github.com/u2ros/python-qt-multithreading - U2ros

4
你可以通过调用exit()或quit()来停止线程。在极端情况下,您可能希望强制终止正在执行的线程。但是,这样做是危险和不鼓励的。请阅读terminate()和setTerminationEnabled()的文档以获取详细信息。
来源: https://doc.qt.io/qtforpython/PySide2/QtCore/QThread.html

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接