Qthread在关闭Python PyQt的GUI时仍在工作。

3

我的代码有线程,但是当我关闭GUI时,它仍然在后台工作。如何停止线程?是否有stop()、close()之类的方法?我不使用signal和slots,我必须使用吗?

from PyQt4 import QtGui, QtCore
import sys
import time
import threading

class Main(QtGui.QMainWindow):
    def __init__(self, parent=None):
        super(Main, self).__init__(parent)
        self.kac_ders=QtGui.QComboBox()
        self.bilgi_cek=QtGui.QPushButton("Save")
        self.text=QtGui.QLineEdit()
        self.widgetlayout=QtGui.QFormLayout()
        self.widgetlar=QtGui.QWidget()
        self.widgetlar.setLayout(self.widgetlayout)
        self.bilgiler=QtGui.QTextBrowser()
        self.bilgi_cek.clicked.connect(self.on_testLoop)
        self.scrollArea = QtGui.QScrollArea()
        self.scrollArea.setWidgetResizable(True)
        self.scrollArea.setWidget(self.widgetlar)
        self.analayout=QtGui.QVBoxLayout()
        self.analayout.addWidget(self.text)
        self.analayout.addWidget(self.bilgi_cek)
        self.analayout.addWidget(self.bilgiler)
        self.centralWidget=QtGui.QWidget()
        self.centralWidget.setLayout(self.analayout)
        self.setCentralWidget(self.centralWidget)

    def on_testLoop(self):
        self.c_thread=threading.Thread(target=self.kontenjan_ara)
        self.c_thread.start()

    def kontenjan_ara(self):

        while(1):
                self.bilgiler.append(self.text.text())
                time.sleep(10)


app = QtGui.QApplication(sys.argv)
myWidget = Main()
myWidget.show()
app.exec_()

1
此代码不是线程安全的。您不允许从次要线程访问GUI对象(如self.text)。您应该重构您的代码,使用QThread和信号槽,或者仅使用一个线程和QTimer(因为大多数时间线程在睡眠)。 - three_pineapples
2个回答

4
一些事项:
  1. 不应该从主线程外调用GUI代码。GUI元素不是线程安全的。 self.kontenjan_ara 更新和读取GUI元素,它不应是您thread 的目标。

  2. 几乎所有情况下,您应该使用QThreads而不是Python线程。它们与Qt中的事件和信号系统很好地集成。

如果只需要每隔几秒钟运行一次某些内容,则可以使用QTimer

def __init__(self, parent=None):
    ...
    self.timer = QTimer(self)
    self.timer.timeout.connect(self.kontenjan_ara)
    self.timer.start(10000)

def kontenjan_ara(self):
    self.bilgiler.append(self.text.text())

如果您的线程操作更加复杂,您可以创建一个工作线程,并使用信号在工作线程和主GUI线程之间传递数据。
class Worker(QObject):

    work_finished = QtCore.pyqtSignal(object)

    @QtCore.pyqtSlot()
    def do_work(self):
        data = 'Text'
        while True:
            # Do something with data and pass back to main thread
            data = data + 'text'
            self.work_finished.emit(data)
            time.sleep(10)


class MyWidget(QtGui.QWidget):

    def __init__(self, ...)
        ...
        self.worker = Worker()
        self.thread = QtCore.QThread(self)
        self.worker.work_finished.connect(self.on_finished)
        self.worker.moveToThread(self.thread)
        self.thread.started.connect(self.worker.do_work)
        self.thread.start()

    @QtCore.pyqtSlot(object)
    def on_finished(self, data):
        self.bilgiler.append(data)
        ...

当主线程退出事件循环时,Qt会自动停止所有子线程。


这比我的答案好得多,更加正确,但我认为 OP 想要在每次按钮点击时启动一个工作线程,并希望该工作线程从 GUI 中读取,而不仅是异步通知它。 - pistache

2
我选择重新编写这个答案,因为我没有正确地查看问题的上下文。正如其他答案和评论所说,您的代码缺乏线程安全性。
修复此问题的最佳方法是尝试真正地“考虑线程”,限制自己仅使用在同一线程中存在的对象或已知为“线程安全”的函数。
添加一些信号和插槽可能有所帮助,但也许您想再次思考一下原始问题。在您当前的代码中,每次按下按钮时,都会启动一个新线程,该线程将每隔10秒执行2个操作: - 从self.text中读取一些文本 - 将其附加到self.bilgiler 这两个操作都不是线程安全的,必须从拥有这些对象的线程(主线程)调用。您希望使工作线程“调度和等待”读取和附加操作,而不是简单地“执行”它们。
我建议使用其他答案(通过使用与Qt事件循环良好集成的正确QThreads自动解决线程停止问题),这将使您使用更干净、更与Qt集成的方法。
您可能还想重新考虑您的问题,因为也许有一种更简单的方法来解决它,例如:不每次点击“bilgi_cek”时都生成线程,或者使用“Queue”对象使您的工作线程完全与您的 GUI 无关,并且仅使用线程安全的对象进行交互。
祝好运,如果我造成了任何困惑,请原谅。我的原始答案仍然可在此处获得。我认为将另一个答案标记为此问题的有效答案会很明智。

谢谢,它起作用了,但我有一些问题。当我关闭GUI时,如何设置self.closed?self.closed = threading.Event()那行代码是否理解我何时关闭GUI?它必须是信号,不是吗? - Tony Stark
这可以通过重写 closeEvent 并调用 self.closed.set() 来处理。请注意,另一个答案可能是您问题的更好解决方案,因为它直接使用 Qt 功能使线程在循环停止时终止,并且还修复了代码中缺乏线程安全性的问题。 - pistache
实际上,我真的没有关于PyQt的经验和能力,你的解决方案解决了我的问题,但现在出现了一些新问题,我的代码不像那个例子那么简单,而且我的代码中有两个线程。现在,我甚至不知道如何调用其他类的变量等。其他解决方案的第一部分起作用了,但我无法理解第二部分。我正在考虑如何与我的代码同步,非常感谢您的关心,再次非常感谢您。 - Tony Stark

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