2013-04-14 1 views
5

У меня есть QDialog, который создает QThread, чтобы выполнить некоторую работу, сохраняя отзывчивость пользовательского интерфейса на основе приведенной здесь структуры: How To Really, Truly Use QThreads; The Full Explanation. Однако, если отвергнуть() вызываются (из-за нажатие пользователя отмены или закрытия диалогового окна), а поток все еще работает, я получаю сообщение об ошибке:Прекращение QThread изящно на QDialog reject()

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 и, следовательно, не будут собраны с помощью ГХ, прежде чем она заканчивается 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

Смежные вопросы