Como Nathan W señala, la manera de hacer esto es con multithreading, 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
y luego moverlo a un QThread
(es decir, la forma "correcta" de hacerlo). Este ejemplo calcula el área total de todas las características de una capa vectorial (¡utilizando la nueva API de 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 utilizar el trabajador necesitamos initalizarlo con una capa vectorial, moverlo al hilo, conectar algunas señales, y luego iniciarlo. Probablemente lo mejor sea mirar el blog enlazado arriba para entender lo que está pasando 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 lo que hay dentro del
run()
del trabajador está dentro de una sentencia try-except. Es difícil recuperarse cuando tu código se bloquea dentro de un hilo. Emite el traceback a través de la señal de error, que yo suelo conectar al QgsMessageLog
.
- La señal de finalización indica al método conectado si el proceso se ha completado con éxito, así como el resultado.
- La señal de progreso sólo se llama cuando cambia el porcentaje de finalización, en lugar de hacerlo una vez por cada función. Esto evita que demasiadas llamadas para actualizar la barra de progreso ralenticen el proceso del trabajador, lo que anularía el objetivo de ejecutar el trabajador en otro hilo: separar el cálculo de la interfaz de usuario.
- El trabajador implementa un
kill()
que permite que la función termine con gracia. No intente utilizar el método terminate()
método en QThread
- ¡podrían pasar cosas malas!
Asegúrese de llevar un registro de su thread
y worker
en algún lugar de la estructura de tu plugin. Qt se enfada si no lo haces. La forma más fácil de hacerlo es almacenarlos en tu diálogo cuando los creas, por ejemplo
thread = self.thread = QtCore.QThread()
worker = self.worker = Worker(layer)
O puedes dejar que Qt se apropie del QThread:
thread = QtCore.QThread(self)
Me llevó mucho tiempo buscar todos los tutoriales para crear esta plantilla, pero desde entonces la he estado reutilizando por todas partes.