Como señala Nathan W , la forma de hacerlo es con subprocesos múltiples, pero subclasificar QThread no es la mejor práctica. Ver aquí: http://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation/
Vea a continuación un ejemplo de cómo crear un QObject
, luego muévalo a un QThread
(es decir, la forma "correcta" de hacerlo). Este ejemplo calcula el área total de todas las características en una capa vectorial (¡usando la nueva API QGIS 2.0!).
Primero, creamos el objeto "trabajador" que hará el trabajo pesado por nosotros:
class Worker(QtCore.QObject):
def __init__(self, layer, *args, **kwargs):
QtCore.QObject.__init__(self, *args, **kwargs)
self.layer = layer
self.total_area = 0.0
self.processed = 0
self.percentage = 0
self.abort = False
def run(self):
try:
self.status.emit('Task started!')
self.feature_count = self.layer.featureCount()
features = self.layer.getFeatures()
for feature in features:
if self.abort is True:
self.killed.emit()
break
geom = feature.geometry()
self.total_area += geom.area()
self.calculate_progress()
self.status.emit('Task finished!')
except:
import traceback
self.error.emit(traceback.format_exc())
self.finished.emit(False, self.total_area)
else:
self.finished.emit(True, self.total_area)
def calculate_progress(self):
self.processed = self.processed + 1
percentage_new = (self.processed * 100) / self.feature_count
if percentage_new > self.percentage:
self.percentage = percentage_new
self.progress.emit(self.percentage)
def kill(self):
self.abort = True
progress = QtCore.pyqtSignal(int)
status = QtCore.pyqtSignal(str)
error = QtCore.pyqtSignal(str)
killed = QtCore.pyqtSignal()
finished = QtCore.pyqtSignal(bool, float)
Para usar el trabajador necesitamos inicializarlo con una capa vectorial, moverlo al hilo, conectar algunas señales y luego iniciarlo. Probablemente sea mejor mirar el blog vinculado anteriormente para comprender lo que está sucediendo aquí.
thread = QtCore.QThread()
worker = Worker(layer)
worker.moveToThread(thread)
thread.started.connect(worker.run)
worker.progress.connect(self.ui.progressBar)
worker.status.connect(iface.mainWindow().statusBar().showMessage)
worker.finished.connect(worker.deleteLater)
thread.finished.connect(thread.deleteLater)
worker.finished.connect(thread.quit)
thread.start()
Este ejemplo ilustra algunos puntos clave:
- Todo dentro del
run()
método del trabajador está dentro de una declaración try-except. Es difícil recuperarse cuando su código se bloquea dentro de un hilo. Emite el rastreo a través de la señal de error, que generalmente conecto con el QgsMessageLog
.
- La señal finalizada le dice al método conectado si el proceso se completó con éxito, así como el resultado.
- La señal de progreso solo se llama cuando cambia el porcentaje completado, en lugar de una vez para cada función. Esto evita demasiadas llamadas para actualizar la barra de progreso, lo que ralentiza el proceso del trabajador, lo que anularía el punto de ejecutar el trabajador en otro hilo: separar el cálculo de la interfaz de usuario.
- El trabajador implementa un
kill()
método que permite que la función finalice correctamente. No intentes usar el terminate()
método QThread
, ¡podrían pasar cosas malas!
Asegúrese de realizar un seguimiento de sus objetos thread
y worker
en algún lugar de su estructura de complementos. Qt se enoja si no lo haces. La forma más fácil de hacer esto es almacenarlos en su diálogo cuando los cree, por ejemplo:
thread = self.thread = QtCore.QThread()
worker = self.worker = Worker(layer)
O puede dejar que Qt tome posesión del QThread:
thread = QtCore.QThread(self)
Me llevó mucho tiempo desenterrar todos los tutoriales para armar esta plantilla, pero desde entonces la he reutilizado por todas partes.