2013-04-14 42 views
5

我有一個QDialog創建一個QThread來做一些工作,同時保持UI響應,基於這裏給出的結構:How To Really, Truly Use QThreads; The Full Explanation。但是,如果拒絕()被調用(由於用戶按下取消或關閉對話框),而線程仍在運行,我得到一個錯誤:QDialog優雅地終止QThial拒絕()

QThread: Destroyed while thread is still running

我想什麼發生是循環的工作人員提早休息,然後在後臺做一些清理工作(例如清理一些隊列,發出信號)。我已經設法用我自己的「取消」功能來做到這一點,但是如何讓它與reject()(以及可以調用的所有方法)很好地發揮作用?我不希望對話框阻止等待清理 - 它應該繼續在後臺運行,然後優雅地退出。

請參閱下面的示例代碼,其中有問題。任何幫助將不勝感激。

#!/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() 

回答

8

您的問題是: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的所擁有,因此不會被GC它是由Qt的正常終止之前收集的。其實,這個方法只是讓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() 
+0

使用這種方法是有可能得到的主要應用程序等待的線程退出前清理?我將代碼作爲更大型應用程序(QGIS)中的插件運行。 – Snorfalorpagus

+0

@snorfalorpagus:我更新了我的帖子。我認爲使用標誌是停止線程最安全的方法。 – nymk