11 votos

¿Cómo puedo evitar que Qgis sea detectado como "no responde" cuando se ejecuta un plugin pesado?

Utilizo la siguiente línea para informar al usuario sobre el estado:

iface.mainWindow().statusBar().showMessage("Status:" + str(i))

El plugin tarda unos 2 minutos en ejecutarse en mi conjunto de datos pero windows lo detecta como "no responde" y deja de mostrar las actualizaciones de estado. Para un nuevo usuario esto no es tan bueno ya que parece que el programa se ha estrellado.

¿Hay alguna solución para que el usuario no se quede sin saber el estado del plugin?

17voto

Fitzcarraldo Puntos 423

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.

0 votos

Gracias, esto era exactamente lo que estaba buscando y fue muy útil. Estoy acostumbrado a los hilos i C #, pero no pensó en él en python.

0 votos

Sí, esta es la forma correcta.

1 votos

¿Debe haber un "self." delante de la capa en "features = layer.getFeatures()"? -> "features = self.layer.getFeatures()"

4voto

Jauder Ho Puntos 3172

La única forma real de hacerlo es mediante el multihilo.

class MyLongRunningStuff(QThread):
    progressReport = pyqtSignal(str)
    def __init__(self):
       QThread.__init__(self)

    def run(self):
       # do your long runnning thing
       self.progressReport.emit("I just did X")

 thread = MyLongRunningStuff()
 thread.progressReport.connect(self.updatetheuimethod)
 thread.start()

Algunas lecturas adicionales http://joplaete.wordpress.com/2010/07/21/threading-with-pyqt4/

Nota A algunas personas no les gusta heredar de QThread, y aparentemente esta no es la forma "correcta" de hacerlo, pero funciona así....

0 votos

:) Parece una bonita y sucia manera de hacerlo. Algunas veces el estilo no es necesario. Para esta vez(la primera en pyqt) creo que voy a ir con la forma correcta ya que estoy acostumbrado a eso en C#.

2 votos

No es una forma sucia, era la forma antigua de hacerlo.

3voto

s1d Puntos 91

Como esta pregunta es relativamente antigua, merece una actualización. Con QGIS 3 hay una aproximación con QgsTask.fromFunction(), QgsProcessingAlgRunnerTask() y QgsApplication.taskManager().addTask().

Más información, por ejemplo, en Uso de hilos en PyQGIS3 POR MARCO BERNASOCCHI

i-Ciencias.com

I-Ciencias es una comunidad de estudiantes y amantes de la ciencia en la que puedes resolver tus problemas y dudas.
Puedes consultar las preguntas de otros usuarios, hacer tus propias preguntas o resolver las de los demás.

Powered by:

X