Jak zauważa Nathan W , sposobem na to jest wielowątkowość, ale podklasa QThread nie jest najlepszą praktyką. Zobacz tutaj: http://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation/
Zobacz poniżej przykład tworzenia a QObject
, a następnie przenieś go do QThread
(tj. „Prawidłowego” sposobu na zrobienie tego). Ten przykład oblicza całkowity obszar wszystkich funkcji w warstwie wektorowej (przy użyciu nowego API QGIS 2.0!).
Najpierw tworzymy obiekt „roboczy”, który wykona za nas ciężkie podnoszenie:
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)
Aby użyć pracownika, musimy zainicjować go warstwą wektorową, przenieść go do wątku, podłączyć niektóre sygnały, a następnie uruchomić. Prawdopodobnie najlepiej jest zajrzeć na powyższego bloga, aby zrozumieć, co się tutaj dzieje.
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()
Ten przykład ilustruje kilka kluczowych punktów:
- Wszystko w
run()
metodzie pracownika znajduje się w instrukcji try-wyjątkiem. Trudno go odzyskać, gdy kod ulega awarii w wątku. Emituje ślad poprzez sygnał błędu, który zwykle podłączam do QgsMessageLog
.
- Gotowy sygnał informuje podłączoną metodę, czy proces zakończył się powodzeniem, a także wynik.
- Sygnał postępu jest wywoływany tylko wtedy, gdy zmienia się procent ukończenia, a nie raz dla każdej funkcji. Zapobiega to zbyt dużej liczbie wywołań, aby zaktualizować pasek postępu, spowalniając proces roboczy, co zniweczyłoby cały punkt uruchamiania pracownika w innym wątku: oddzielenie obliczeń od interfejsu użytkownika.
- Pracownik implementuje
kill()
metodę, która umożliwia łagodne zakończenie funkcji. Nie próbuj używać tej terminate()
metody w QThread
- mogą się zdarzyć złe rzeczy!
Pamiętaj, aby śledzić swoje obiekty thread
i worker
obiekty gdzieś w strukturze wtyczek. Qt się denerwuje, jeśli nie. Najprostszym sposobem na to jest przechowywanie ich w oknie dialogowym podczas ich tworzenia, np .:
thread = self.thread = QtCore.QThread()
worker = self.worker = Worker(layer)
Lub możesz pozwolić Qt przejąć na własność QThread:
thread = QtCore.QThread(self)
Dużo czasu zajęło mi wykopanie wszystkich samouczków, aby złożyć ten szablon razem, ale od tego czasu używam go wszędzie.