Ejemplo de la manera correcta de usar QThread en PyQt?

Estoy tratando de aprender a usar QThreads en una aplicación PyQt Gui. Tengo cosas que funcionan por un tiempo, con (generalmente) puntos donde podría actualizar una Gui, pero me gustaría dividir el trabajo principal en su propio hilo (a veces las cosas se atascan, y sería bueno tener un botón cancelar / intentar de nuevo, que obviamente no funciona si la Gui está congelada porque el bucle principal está bloqueado).

He leído https://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation/ . Esa página dice que volver a implementar el método de run no es la forma de hacerlo. El problema que tengo es encontrar un ejemplo de PyQt que tenga un subproceso principal que haga el gui y un subproceso de trabajo que no lo haga de esa manera. La publicación del blog es para C ++, así que mientras que los ejemplos ayudan, todavía estoy un poco perdido. ¿Alguien me puede indicar un ejemplo de la forma correcta de hacerlo en Python?

Este es un ejemplo práctico de un subproceso de trabajo independiente que puede enviar y recibir señales para permitirle comunicarse con una GUI.

Hice dos botones simples, uno que inicia un cálculo largo en un subproceso separado y otro que termina de inmediato el cálculo y restablece el subproceso de trabajo.

Terminar a la fuerza un hilo como se hace aquí no es generalmente la mejor manera de hacer las cosas, pero hay situaciones en las que siempre salir con gracia no es una opción.

 from PyQt4 import QtGui, QtCore import sys import random class Example(QtCore.QObject): signalStatus = QtCore.pyqtSignal(str) def __init__(self, parent=None): super(self.__class__, self).__init__(parent) # Create a gui object. self.gui = Window() # Create a new worker thread. self.createWorkerThread() # Make any cross object connections. self._connectSignals() self.gui.show() def _connectSignals(self): self.gui.button_cancel.clicked.connect(self.forceWorkerReset) self.signalStatus.connect(self.gui.updateStatus) self.parent().aboutToQuit.connect(self.forceWorkerQuit) def createWorkerThread(self): # Setup the worker object and the worker_thread. self.worker = WorkerObject() self.worker_thread = QtCore.QThread() self.worker.moveToThread(self.worker_thread) self.worker_thread.start() # Connect any worker signals self.worker.signalStatus.connect(self.gui.updateStatus) self.gui.button_start.clicked.connect(self.worker.startWork) def forceWorkerReset(self): if self.worker_thread.isRunning(): print('Terminating thread.') self.worker_thread.terminate() print('Waiting for thread termination.') self.worker_thread.wait() self.signalStatus.emit('Idle.') print('building new working object.') self.createWorkerThread() def forceWorkerQuit(self): if self.worker_thread.isRunning(): self.worker_thread.terminate() self.worker_thread.wait() class WorkerObject(QtCore.QObject): signalStatus = QtCore.pyqtSignal(str) def __init__(self, parent=None): super(self.__class__, self).__init__(parent) @QtCore.pyqtSlot() def startWork(self): for ii in range(7): number = random.randint(0,5000**ii) self.signalStatus.emit('Iteration: {}, Factoring: {}'.format(ii, number)) factors = self.primeFactors(number) print('Number: ', number, 'Factors: ', factors) self.signalStatus.emit('Idle.') def primeFactors(self, n): i = 2 factors = [] while i * i <= n: if n % i: i += 1 else: n //= i factors.append(i) if n > 1: factors.append(n) return factors class Window(QtGui.QWidget): def __init__(self): QtGui.QWidget.__init__(self) self.button_start = QtGui.QPushButton('Start', self) self.button_cancel = QtGui.QPushButton('Cancel', self) self.label_status = QtGui.QLabel('', self) layout = QtGui.QVBoxLayout(self) layout.addWidget(self.button_start) layout.addWidget(self.button_cancel) layout.addWidget(self.label_status) self.setFixedSize(400, 200) @QtCore.pyqtSlot(str) def updateStatus(self, status): self.label_status.setText(status) if __name__=='__main__': app = QtGui.QApplication(sys.argv) example = Example(app) sys.exit(app.exec_()) 

Tiene razón en que es bueno tener un subproceso de trabajo que realiza el procesamiento, mientras que el subproceso principal está haciendo la GUI. Además, PyQt proporciona instrumentación de hilos con un mecanismo de señal / ranura que es seguro para hilos.

Esto puede sonar de interés . En su ejemplo, construyen una GUI

 import sys, time from PyQt4 import QtCore, QtGui class MyApp(QtGui.QWidget): def __init__(self, parent=None): QtGui.QWidget.__init__(self, parent) self.setGeometry(300, 300, 280, 600) self.setWindowTitle('threads') self.layout = QtGui.QVBoxLayout(self) self.testButton = QtGui.QPushButton("test") self.connect(self.testButton, QtCore.SIGNAL("released()"), self.test) self.listwidget = QtGui.QListWidget(self) self.layout.addWidget(self.testButton) self.layout.addWidget(self.listwidget) def add(self, text): """ Add item to list widget """ print "Add: " + text self.listwidget.addItem(text) self.listwidget.sortItems() def addBatch(self,text="test",iters=6,delay=0.3): """ Add several items to list widget """ for i in range(iters): time.sleep(delay) # artificial time delay self.add(text+" "+str(i)) def test(self): self.listwidget.clear() # adding entries just from main application: locks ui self.addBatch("_non_thread",iters=6,delay=0.3) 

(ui simple que contiene un widget de lista al que agregaremos algunos elementos haciendo clic en un botón)

A continuación, puede crear nuestra propia clase de hilo, un ejemplo es

 class WorkThread(QtCore.QThread): def __init__(self): QtCore.QThread.__init__(self) def __del__(self): self.wait() def run(self): for i in range(6): time.sleep(0.3) # artificial time delay self.emit( QtCore.SIGNAL('update(QString)'), "from work thread " + str(i) ) self.terminate() 

Usted redefine el método run() . Puede encontrar una alternativa a terminate() , consulte el tutorial.