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 thread
và worker
cá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.