【问题标题】:Terminating QThread gracefully on QDialog reject()在 QDialog reject() 上优雅地终止 QThread
【发布时间】:2013-04-06 23:18:10
【问题描述】:

我有一个 QDialog,它创建一个 QThread 来做一些工作,同时保持 UI 响应,基于这里给出的结构:How To Really, Truly Use QThreads; The Full Explanation。但是,如果在线程仍在运行时调用拒绝()(由于用户按下取消或关闭对话框),我会收到错误:

QThread:在线程仍在运行时销毁

我希望工作人员中的循环尽早中断,然后在后台进行一些清理(例如,清除一些队列,发出信号)。我已经设法用我自己的“取消”函数来做到这一点,但是我如何让它与拒绝()(以及它可以被调用的所有多种方式)很好地配合?我不希望对话框阻止等待清理 - 它应该继续在后台运行,然后正常退出。

请参阅下面显示问题的示例代码。任何帮助将不胜感激。

#!/usr/bin/env python

from PyQt4 import QtCore, QtGui
import sys
import time

class Worker(QtCore.QObject):
    def __init__(self):
        QtCore.QObject.__init__(self)

    def process(self):
        # dummy worker process
        for n in range(0, 10):
            print 'process {}'.format(n)
            time.sleep(0.5)
        self.finished.emit()

    finished = QtCore.pyqtSignal()

class Dialog(QtGui.QDialog):
    def __init__(self):
        QtGui.QDialog.__init__(self)
        self.init_ui()

    def init_ui(self):
        self.layout = QtGui.QVBoxLayout(self)
        self.btn_run = QtGui.QPushButton('Run', self)
        self.layout.addWidget(self.btn_run)
        self.btn_cancel = QtGui.QPushButton('Cancel', self)
        self.layout.addWidget(self.btn_cancel)

        QtCore.QObject.connect(self.btn_run, QtCore.SIGNAL('clicked()'), self.run)
        QtCore.QObject.connect(self.btn_cancel, QtCore.SIGNAL('clicked()'), self.reject)

        self.show()
        self.raise_()

    def run(self):
        # start the worker thread
        self.thread = QtCore.QThread()
        self.worker = Worker()
        self.worker.moveToThread(self.thread)
        QtCore.QObject.connect(self.thread, QtCore.SIGNAL('started()'), self.worker.process)
        QtCore.QObject.connect(self.worker, QtCore.SIGNAL('finished()'), self.thread.quit)
        QtCore.QObject.connect(self.worker, QtCore.SIGNAL('finished()'), self.worker.deleteLater)
        QtCore.QObject.connect(self.thread, QtCore.SIGNAL('finished()'), self.thread.deleteLater)
        self.thread.start()

def main():
    app = QtGui.QApplication(sys.argv)
    dlg = Dialog()
    ret = dlg.exec_()

if __name__ == '__main__':
    main()

【问题讨论】:

    标签: python multithreading qt pyqt


    【解决方案1】:

    您的问题是:self.thread 在对话框关闭或按下取消按钮后被 Python 释放,而 Qt 线程仍在运行。

    为避免这种情况,您可以指定该线程的父级。例如,

    
        def run(self):
            # start the worker thread
            self.thread = QtCore.QThread(self)
            self.worker = Worker()
            self.worker.moveToThread(self.thread)
            QtCore.QObject.connect(self.thread, QtCore.SIGNAL('started()'), self.worker.process)
            QtCore.QObject.connect(self.worker, QtCore.SIGNAL('finished()'), self.thread.quit)
            QtCore.QObject.connect(self.worker, QtCore.SIGNAL('finished()'), self.worker.deleteLater)
            QtCore.QObject.connect(self.thread, QtCore.SIGNAL('finished()'), self.thread.deleteLater)
            self.thread.start()
    

    然后它将由 Qt 而不是 PyQt 拥有,因此在 Qt 优雅地终止它之前不会被 GC 收集。 实际上,这种方法只是让 Qt 不抱怨,并没有完全解决问题。

    为了优雅地终止线程,常用的方法是使用一个标志来通知工作函数停止。
    例如:

    class Worker(QtCore.QObject):
        def __init__(self):
            QtCore.QObject.__init__(self)
    
        def process(self):
            # dummy worker process
            self.flag = False
            for n in range(0, 10):
                if self.flag:
                    print 'stop'
                    break
                print 'process {}'.format(n)
                time.sleep(0.5)
            self.finished.emit()
    
        finished = QtCore.pyqtSignal()
    
    class Dialog(QtGui.QDialog):
        def __init__(self, parent=None):
            QtGui.QDialog.__init__(self, parent)
            self.init_ui()
    
        def init_ui(self):
            self.layout = QtGui.QVBoxLayout(self)
            self.btn_run = QtGui.QPushButton('Run', self)
            self.layout.addWidget(self.btn_run)
            self.btn_cancel = QtGui.QPushButton('Cancel', self)
            self.layout.addWidget(self.btn_cancel)
    
            QtCore.QObject.connect(self.btn_run, QtCore.SIGNAL('clicked()'), self.run)
            QtCore.QObject.connect(self.btn_cancel, QtCore.SIGNAL('clicked()'), self.reject)
    
            QtCore.QObject.connect(self, QtCore.SIGNAL('rejected()'), self.stop_worker)
    
            self.show()
            self.raise_()
    
        def stop_worker(self):
            print 'stop'
            self.worker.flag = True
    
        def run(self):
            # start the worker thread
            self.thread = QtCore.QThread(self)
            self.worker = Worker()
            self.worker.moveToThread(self.thread)
            QtCore.QObject.connect(self.thread, QtCore.SIGNAL('started()'), self.worker.process)
            QtCore.QObject.connect(self.worker, QtCore.SIGNAL('finished()'), self.thread.quit)
            QtCore.QObject.connect(self.worker, QtCore.SIGNAL('finished()'), self.worker.deleteLater)
            QtCore.QObject.connect(self.thread, QtCore.SIGNAL('finished()'), self.thread.deleteLater)
            self.thread.start()
    

    【讨论】:

    • 使用这种方法是否可以让主应用程序在退出前等待任何线程清理?我在更大的应用程序 (QGIS) 中将代码作为插件运行。
    • @snorfalorpagus:我更新了我的帖子。我认为使用标志是停止线程的最安全方法。
    猜你喜欢
    • 2021-06-04
    • 1970-01-01
    • 2019-08-01
    • 1970-01-01
    • 2011-02-14
    • 1970-01-01
    • 2011-01-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多