Як зазначає Натан У , спосіб зробити це за допомогою багатопотокової, але підкласифікація QThread - не найкраща практика. Дивіться тут: http://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation/
Нижче див. Приклад того, як створити файл QObject
, а потім перемістити його в QThread
(тобто "правильний" спосіб зробити це). Цей приклад обчислює загальну площу всіх функцій у векторному шарі (використовуючи новий API QGIS 2.0!).
По-перше, ми створюємо об'єкт "робітник", який буде робити важкий підйом для нас:
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()
методу працівника, знаходиться всередині випробування, окрім випробувань. Важко відновити, коли ваш код виходить з ладу всередині потоку. Він випромінює відстеження через сигнал помилки, до якого я зазвичай підключаюсь QgsMessageLog
.
- Готовий сигнал повідомляє підключеному методу, якщо процес завершено успішно, а також результат.
- Сигнал прогресу викликається лише тоді, коли відсоток завершується, а не один раз для кожної функції. Це запобігає занадто багато викликів для оновлення панелі прогресу, сповільнюючи процес роботи, що призведе до поразки всієї точки запуску робочого в іншій потоці: відокремити обчислення від користувальницького інтерфейсу.
- Робітник реалізує
kill()
метод, який дозволяє функції граціозно закінчуватися. Не намагайтеся використовувати terminate()
метод у цьому QThread
- погані речі можуть статися!
Обов’язково слідкуйте за своїми thread
та worker
об’єктами десь у вашій структурі плагінів. Qt сердиться, якщо ви цього не зробите. Найпростіший спосіб зробити це - зберегти їх у діалоговому вікні під час їх створення, наприклад:
thread = self.thread = QtCore.QThread()
worker = self.worker = Worker(layer)
Або ви можете дозволити Qt брати право власності на QThread:
thread = QtCore.QThread(self)
Мені знадобилося багато часу, щоб викопати всі підручники, щоб скласти цей шаблон разом, але з тих пір я його повторно використовую.