무거운 플러그인을 실행할 때 Qgi가 "응답하지 않음"으로 감지되지 않도록하려면 어떻게합니까?


10

다음 줄을 사용하여 사용자에게 상태를 알려줍니다.

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

플러그인은 내 데이터 세트에서 실행하는 데 약 2 분이 걸리지 만 Windows는 "응답하지 않음"으로 감지하고 상태 업데이트 표시를 중지합니다. 새로운 사용자에게는 프로그램이 충돌 한 것처럼 보이기 때문에 좋지 않습니다.

플러그인 상태와 관련하여 사용자가 어둠 속에 남아 있지 않도록 해결 방법이 있습니까?

답변:


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를 사용하여 벡터 레이어에있는 모든 기능의 총 면적을 계산합니다.

먼저, 우리를 위해 무거운 작업을 수행 할 "작업자"개체를 만듭니다.

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 # 스레드에 익숙하지만 파이썬에서는 그것을 생각하지 않았다.
Johan Holtby

예, 이것이 올바른 방법입니다.
Nathan W

1
"자기"가 있어야합니다. "features = layer.getFeatures ()"에서 레이어 앞에? -> "기능 = self.layer.getFeatures ()"
Håvard Tveite

@ HåvardTveite 당신이 맞습니다. 답변에서 코드를 수정했습니다.
Snorfalorpagus

작성중인 처리 스크립트에 대해이 패턴을 따르려고하는데 작동하는데 문제가 있습니다. 이 예제를 스크립트 파일로 복사하고 필요한 import 문을 추가하고 worker.progress.connect(self.ui.progressBar)다른 것으로 변경 했지만 qgis-bin을 실행할 때마다 충돌합니다. 파이썬 코드 또는 qgis 디버깅 경험이 없습니다. 내가 얻는 것은 Access violation reading location 0x0000000000000008무언가가 null 인 것처럼 보입니다. 처리 스크립트에서 이것을 사용할 수없는 일부 설정 코드가 있습니까?
TJ Rockefeller

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
그것은 더러운 방법이 아닙니다. 옛날 방식이었습니다.
Nathan W

2

이 질문은 비교적 오래되었으므로 업데이트 할 가치가 있습니다. QGIS 3에는 QgsTask.fromFunction (), QgsProcessingAlgRunnerTask () 및 QgsApplication.taskManager (). addTask ()에 대한 접근 방식이 있습니다.

예를 들어 PyQGIS3 에서 쓰레드 사용하기 BY MARCO BERNASOCCHI

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.