Làm cách nào để ngăn Qgis không bị phát hiện vì không phản hồi được khi chạy plugin nặng?


10

Tôi sử dụng dòng folowing để thông báo cho người dùng về trạng thái:

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

Plugin mất khoảng 2 phút để chạy trên tập dữ liệu của tôi nhưng các cửa sổ phát hiện nó là "không phản hồi" và ngừng hiển thị các cập nhật trạng thái. Đối với người dùng mới, điều này không tốt lắm vì có vẻ như chương trình đã bị sập.

Có bất kỳ công việc nào xung quanh để người dùng không bị bỏ lại trong bóng tối liên quan đến trạng thái của plugin không?

Câu trả lời:


13

Như Nathan W chỉ ra, cách để thực hiện điều này là với đa luồng, nhưng phân lớp QThread không phải là cách thực hành tốt nhất. Xem tại đây: http://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation/

Xem bên dưới một ví dụ về cách tạo một QObject, sau đó di chuyển nó đến một QThread(tức là cách "chính xác" để làm điều đó). Ví dụ này tính toán tổng diện tích của tất cả các tính năng trong một lớp vectơ (sử dụng API QGIS 2.0 mới!).

Đầu tiên, chúng ta tạo đối tượng "worker" sẽ thực hiện công việc nặng nhọc cho chúng ta:

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)

Để sử dụng công nhân, chúng ta cần kích hoạt nó bằng một lớp vectơ, di chuyển nó đến luồng, kết nối một số tín hiệu, sau đó khởi động nó. Có lẽ tốt nhất là nhìn vào blog được liên kết ở trên để hiểu những gì đang diễn ra ở đây.

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

Ví dụ này minh họa một vài điểm chính:

  • Tất cả mọi thứ bên trong run()phương thức của công nhân đều nằm trong một câu lệnh ngoại trừ thử. Thật khó để phục hồi khi mã của bạn gặp sự cố trong một luồng. Nó phát ra dấu vết thông qua tín hiệu lỗi mà tôi thường kết nối với QgsMessageLog.
  • Tín hiệu hoàn thành cho biết phương thức được kết nối nếu quá trình hoàn thành thành công, cũng như kết quả.
  • Tín hiệu tiến trình chỉ được gọi khi phần trăm hoàn thành thay đổi, thay vì một lần cho mỗi tính năng. Điều này ngăn quá nhiều cuộc gọi để cập nhật thanh tiến trình làm chậm quá trình worker, điều này sẽ đánh bại toàn bộ điểm chạy worker trong luồng khác: tách tính toán khỏi giao diện người dùng.
  • Công nhân thực hiện một kill()phương thức, cho phép hàm kết thúc một cách duyên dáng. Đừng thử và sử dụng terminate()phương pháp trong QThread- những điều xấu có thể xảy ra!

Hãy chắc chắn để theo dõi của bạn threadworkercác đối tượng ở đâu đó trong cấu trúc plugin của bạn. Qt tức giận nếu bạn không. Cách dễ nhất để làm điều này là lưu trữ chúng trong hộp thoại của bạn khi bạn tạo chúng, ví dụ:

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

Hoặc bạn có thể để Qt sở hữu QThread:

thread = QtCore.QThread(self)

Tôi đã mất một thời gian dài để đào tất cả các hướng dẫn để đặt mẫu này lại với nhau, nhưng kể từ đó tôi đã sử dụng nó khắp nơi.


Cảm ơn bạn đây chính xác là những gì tôi đang tìm kiếm và nó rất hữu ích! Tôi đã quen với chủ đề i C # nhưng không nghĩ về nó trong python.
Johan Holtby

Vâng, đây là cách chính xác.
Nathan W

1
Nên có một "cái tôi". ở phía trước lớp trong "features = layer.getFeatures ()"? -> "features = self.layer.getFeatures ()"
Håvard Tveite

@ HåvardTveite Bạn đúng. Tôi đã sửa mã trong câu trả lời.
Snorfalorpagus

Tôi đang cố gắng theo mô hình này cho một kịch bản xử lý mà tôi đang viết và tôi gặp khó khăn khi làm cho nó hoạt động. Tôi đã thử sao chép ví dụ này vào một tệp script, thêm các câu lệnh nhập cần thiết và thay đổi worker.progress.connect(self.ui.progressBar)thành một thứ khác, nhưng mỗi lần tôi chạy nó thì qgis-bin bị sập. Tôi không có kinh nghiệm gỡ lỗi mã python hoặc qgis. Tất cả tôi nhận được là Access violation reading location 0x0000000000000008vì vậy có vẻ như một cái gì đó là null. Có một số mã thiết lập bị thiếu để có thể sử dụng mã này trong tập lệnh xử lý không?
TJ Rockefeller

4

Cách thực sự duy nhất của bạn để làm điều này là bằng cách đa luồng.

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

Một số đọc thêm http://joplaete.wordpress.com/2010/07/21/threading-with-pyqt4/

Lưu ý Một số người không thích kế thừa từ QThread và dường như đây không phải là cách "chính xác" để làm điều đó, nhưng nó hoạt động rất tốt ....


:) Có vẻ như một cách bẩn đẹp để làm điều đó. Một số lần phong cách là không cần thiết. Lần này (lần đầu tiên trong pyqt) tôi nghĩ rằng tôi sẽ đi theo cách chính xác vì tôi đã quen với điều đó trong C #.
Johan Holtby

2
Đó không phải là một cách bẩn thỉu, đó là cách làm cũ.
Nathan W

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.