Як я можу запобігти виявленню Qgis як "не відповідає" під час запуску важкого плагіна?


10

Я використовую наступний рядок, щоб повідомити користувача про стан:

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

На моє набір даних плагін займає близько 2 хв, але Windows визначає його як "не відповідає" і перестає показувати оновлення стану. Для нового користувача це не так добре, оскільки, схоже, програма вийшла з ладу.

Чи є якась робота навколо, щоб користувач не залишався в темряві щодо стану плагіна?

Відповіді:


13

Як зазначає Натан У , спосіб зробити це за допомогою багатопотокової, але підкласифікація 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)

Мені знадобилося багато часу, щоб викопати всі підручники, щоб скласти цей шаблон разом, але з тих пір я його повторно використовую.


Дякую, саме це я шукав, і це було дуже корисно! Я звик до потоків i C #, але не думав про це в python.
Йохан Холтбі

Так, це правильний шлях.
Nathan W

1
Чи має бути "я". перед шаром у "особливостях = layer.getFeatures ()"? -> "характеристики = self.layer.getFeatures ()"
Håvard Tveite

@ HåvardTveite Ви праві. Я зафіксував код у відповіді.
Snorfalorpagus

Я намагаюся дотримуватися цієї схеми для сценарію обробки, який я пишу, і у мене виникають проблеми з його роботою. Я спробував скопіювати цей приклад у файл сценарію, додав необхідні заяви про імпорт та змінив worker.progress.connect(self.ui.progressBar)щось інше, але кожен раз, коли я запускаю його, qgis-bin виходить з ладу. У мене немає досвіду налагодження коду python або qgis. Все, що я отримую, Access violation reading location 0x0000000000000008так здається, що щось недійсне. Чи є якийсь встановлений код, який відсутній, щоб мати можливість використовувати це в сценарії обробки?
TJ Рокфеллер

4

Ваш єдиний вірний спосіб зробити це - багатопотокове читання.

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

Деякі додаткові читання http://joplaete.wordpress.com/2010/07/21/threading-with-pyqt4/

Примітка. Деяким людям не подобається успадковувати з QThread, і, мабуть, це не "правильний" спосіб зробити це, але це працює так ...


:) Це схоже на приємний брудний спосіб зробити це. Інколи стиль не потрібен. На цей час (перший у pyqt) я думаю, що я піду правильним шляхом, оскільки звик до цього в C #.
Йохан Холтбі

2
Це не брудний спосіб, це був старий спосіб зробити це.
Nathan W

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.