Cómo detener un QThread desde la GUI

Esta es una pregunta de seguimiento de una anterior que publiqué anteriormente. El problema es cómo detener (terminar | salir | salir) un QThread de la GUI cuando se usa el método recomendado para NO subclasificar Qthread, sino crear un objeto QObject y luego moverlo a un QThread. A continuación si es un ejemplo de trabajo. Puedo iniciar la GUI y el Qthread y puedo hacer que esta última actualice la GUI. Sin embargo, no puedo detenerlo. Probé varios métodos para qthread (quit (), exit (), e incluso terminate ()) sin éxito. Ayuda muy apreciada.

Aquí está el código completo:

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_()) 

Sé que es hace mucho tiempo, pero acabo de tropezar con el mismo problema.

También he estado buscando una forma adecuada de hacer esto. Finalmente fue fácil. Al salir de la aplicación, la tarea debe detenerse y el subproceso debe dejar de llamar a su método de salir. Ver el método stop_thread en la parte inferior. Y tienes que esperar a que el hilo termine. De lo contrario, aparecerá el mensaje QThread: Destruido mientras el subproceso aún se está ejecutando ‘al salir .

(También cambié mi código para usar 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_()) 

Descubrí que mi pregunta original era en realidad dos preguntas en una: para detener un hilo secundario del principal, necesitas dos cosas:

  1. Ser capaz de comunicarse desde el hilo principal hasta el hilo secundario

  2. Envía la señal adecuada para detener el hilo.

No he podido resolver (2), pero me di cuenta de cómo resolver (1), lo que me dio una solución a mi problema original. En lugar de detener el hilo , puedo detener el procesamiento del hilo (el método longRunning() )

El problema es que un subproceso secundario solo puede responder a las señales si ejecuta su propio bucle de eventos. Un Qthread regular (que es lo que usa mi código) no lo hace. Sin embargo, es bastante fácil subclase QThread a ese efecto:

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

y usé self.simulThread = MyThread() en mi código en lugar del self.simulThread = Qthread() original self.simulThread = Qthread() . Esto asegura que el hilo secundario ejecuta un bucle de eventos. Sin embargo, eso no fue suficiente. El método longRunning() debe tener la oportunidad de procesar realmente el evento que viene desde el hilo principal. Con la ayuda de esta respuesta SO , descubrí que la simple adición de un QApplication.processEvent() en el método longRunning() le dio tal oportunidad al hilo secundario. Ahora puedo detener el procesamiento realizado en el subproceso secundario, aunque no he descubierto cómo detener el propio subproceso.

Para concluir. Mi método longRunning ahora se ve así:

 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() 

y mi hilo de la GUI tiene estas tres líneas que hacen el trabajo (además de la subclase QThread listada arriba):

  self.simulThread = MyThread() self.simulRunner.moveToThread(self.simulThread) self.stopButton.clicked.connect(self.simulRunner.stop) 

¡Los comentarios son bienvenidos!