Как отмечает Натан У , способ сделать это - использовать многопоточность, но использование подклассов 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()
методе работника находится внутри оператора try-кроме. Трудно восстановить, когда ваш код падает в потоке. Он генерирует трассировку через сигнал ошибки, который я обычно подключаю к QgsMessageLog
.
- Готовый сигнал сообщает подключенному методу, если процесс завершен успешно, а также результат.
- Сигнал прогресса вызывается только при изменении процента выполнения, а не один раз для каждой функции. Это предотвращает слишком большое количество вызовов для обновления индикатора выполнения, замедляя рабочий процесс, что лишает смысла запускать рабочий в другом потоке: отделять вычисления от пользовательского интерфейса.
- Работник реализует
kill()
метод, который позволяет функции завершить изящно. Не пытайтесь использовать terminate()
метод в QThread
- плохие вещи могут случиться!
Обязательно отслеживайте ваши объекты thread
и worker
объекты где-то в структуре вашего плагина. Qt рассердится, если вы этого не сделаете. Самый простой способ сделать это - сохранить их в диалоговом окне при их создании, например:
thread = self.thread = QtCore.QThread()
worker = self.worker = Worker(layer)
Или вы можете позволить Qt стать владельцем QThread:
thread = QtCore.QThread(self)
Мне потребовалось много времени, чтобы выкопать все учебники, чтобы собрать этот шаблон вместе, но с тех пор я использовал его повсеместно.