重いプラグインを実行しているときにQgisが「応答しない」として検出されないようにするにはどうすればよいですか?


10

次の行を使用して、ユーザーにステータスを通知します。

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

プラグインが私のデータセットで実行されるのに約2分かかりますが、Windowsはプラグインを「応答なし」として検出し、ステータスの更新の表示を停止します。新しいユーザーにとって、プログラムがクラッシュしたように見えるので、これはあまり良くありません。

プラグインのステータスに関してユーザーが暗闇に放置されないようにするための回避策はありますか?

回答:


13

以下のようネイサンWが指摘、これを行う方法は、マルチスレッドであるが、サブクラスQThreadはベストプラクティスではありません。ここを参照してください:http : //mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation/

以下のを作成しQObject、それをに移動する方法の例を参照してくださいQThread(つまり、「正しい」方法で)。この例では、ベクターレイヤーのすべてのフィーチャの総面積を計算します(新しいQGIS 2.0 APIを使用して!)。

最初に、私たちのために重い作業を行う「ワーカー」オブジェクトを作成します。

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)

ワーカーを使用するには、ベクターレイヤーで初期化する必要があります。ワーカーをスレッドに移動し、いくつかの信号を接続してから開始します。上でリンクされているブログを見てここで何が起こっているのかを理解するのがおそらく最善です。

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()ワーカーのメソッド内のすべてが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 2013

ええ、これは正しい方法です。
Nathan W

1
「自己」があるべきです。「features = layer.getFeatures()」でレイヤーの前に?- > "機能= self.layer.getFeatures()"
ハーバード・トバイト

@HåvardTveiteあなたは正しいです。答えのコードを修正しました。
Snorfalorpagus 2014

私が書いている処理スクリプトに対してこのパターンに従っているのですが、うまく機能しません。この例をスクリプトファイルにコピーして、必要なインポートステートメントを追加し、worker.progress.connect(self.ui.progressBar)他のものに変更しましたが、実行するたびにqgis-binがクラッシュします。Pythonコードやqgisのデバッグ経験はありません。私が得ているのはAccess violation reading location 0x0000000000000008何かがnullのようです。これを処理スクリプトで使用するために欠落しているセットアップコードはありますか?
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/

Note一部の人々はQThreadからの継承を嫌い、そしてこれは明らかにこれを行うための「正しい」方法ではありませんが、それはそうです...


:)それはそれをするためのいい汚い方法のように見えます。時にはスタイルは必要ありません。今回(pyqtの最初)、C#に慣れているので、正しい方法で進むと思います。
Johan Holtby 2013

2
それは汚い方法ではなく、それを行う古い方法でした。
Nathan W

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.