在运行沉重的插件时,如何防止Qgis被检测为“无响应”?


10

我使用以下行来通知用户有关状态:

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

该插件大约需要2分钟才能在我的数据集上运行,但是Windows将其检测为“未响应”并停止显示状态更新。对于新用户来说,这不是很好,因为它看起来好像程序崩溃了。

有没有解决的办法,这样用户就不会对插件的状态感到困惑?

Answers:


13

正如Nathan W所指出的那样,实现此目标的方法是使用多线程,但是对QThread进行子类化不是最佳实践。参见此处:http : //mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation/

参见下面的示例,了解如何创建QObject,然后将其移至的方式QThread(即“正确”的方式)。本示例计算矢量层中所有要素的总面积(使用新的QGIS 2.0 API!)。

首先,我们创建“ worker”对象,它将为我们完成繁重的工作:

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)

要使用worker,我们需要在向量层上初始化它,将其移动到线程中,连接一些信号,然后启动它。最好查看上面链接的博客,以了解此处的情况。

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

此示例说明了一些关键点:

  • run()worker方法中的所有内容都在try-except语句中。当代码在线程内崩溃时,很难恢复。它通过错误信号发出回溯,我通常将其连接到QgsMessageLog
  • 完成的信号告诉连接的方法该过程是否成功完成以及结果。
  • 仅在完成百分比更改时才调用进度信号,而不是对每个功能都调用一次。这样可以防止太多的更新进度条的调用减慢工作进程的速度,从而降低在另一个线程中运行工作进程的意义:将计算与用户界面分开。
  • 工作者实现了一个kill()方法,该方法允许函数正常终止。不要尝试使用该terminate()方法QThread-可能会发生不好的事情!

确保在插件结构中的某个位置跟踪您threadworker对象。如果不这样做,Qt会生气。最简单的方法是在创建它们时将它们存储在对话框中,例如:

thread = self.thread = QtCore.QThread()
worker = self.worker = Worker(layer)

或者,您可以让Qt拥有QThread的所有权:

thread = QtCore.QThread(self)

我花了很长时间来整理所有教程,以便将这个模板放在一起,但是从那时起,我就一直在各处重复使用它。


谢谢你,这正是我一直在寻找的东西,对我很有帮助!我习惯于在C#中线程化,但在python中却没有想到。
Johan Holtby

是的,这是正确的方法。
弥敦道W

1
应该有一个“自我”。在“功能= layer.getFeatures()”中的图层前面?->“功能= self.layer.getFeatures()”
哈佛特维特

@HåvardTveite你是正确的。我已经修复了答案中的代码。
Snorfalorpagus

我正在尝试按照这种模式编写正在编写的处理脚本,但是我无法使其正常工作。我尝试将本示例复制到脚本文件中,添加了必要的import语句,然后更改worker.progress.connect(self.ui.progressBar)为其他内容,但是每次运行它时,qgis-bin都崩溃了。我没有调试python代码或qgis的经验。我得到的只是Access violation reading location 0x0000000000000008看起来好像是空的。是否缺少一些设置代码才能在处理脚本中使用它?
TJ洛克菲勒

4

唯一的真正方法是使用多线程。

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

一些额外的阅读http://joplaete.wordpress.com/2010/07/21/threading-with-pyqt4/

注意有些人不喜欢从QThread继承,显然这不是“正确”的方法,但是它确实可以工作。


:)看起来很不错。有时样式是不必要的。这次(pyqt中的第一个)我想我会采用正确的方式,因为我已经习惯了C#。
Johan Holtby

2
这不是一个肮脏的方法,而是古老的方法。
弥敦道W

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.