Community • 12. Juli 2023

QGIS-Tasks „heavy work“ im Hintergrund erledigen lassen

Die TopDeutschland des Bundesamts für Kartographie und Geodäsie (BKG) ist ein für den mobilen Einsatz aufbereitetes QGIS, welches u.a. im Krisenfall verwendet werden kann. Sie enthält deutschlandweite, mitunter sehr große Geobasisdaten für den Offline-Zugriff.

Die WhereGroup hat im Rahmen dieses Projekts ein PyQGIS-Plugin entwickelt, welches über die BKG-Webdienste eine Komplett- und Teilaktualisierung der einzelnen Datensätze ermöglicht.

Bei der Entwicklung des Plugins hatten wir anfänglich die Herausforderung, dass QGIS immer wieder eingefroren ist, wenn das Plugin eine Funktion zum Download großer Daten oder eine andere umfangreiche Verarbeitung aufgerufen hat.

In solchen Fällen ist die Hintergrundverarbeitung mit QGIS-Tasks (QgsTask) eine bequeme Möglichkeit, eine reaktionsschnelle Benutzeroberfläche beizubehalten.

QgsTask ist gewissermaßen ein Container für den im Hintergrund auszuführenden Code. Es können mehrere Tasks gleichzeitig ausgeführt werden. Normalerweise wird der globale Task-Manager (QgsApplication.taskManager()) verwendet, um die Ausführung der einzelnen Tasks zu steuern.

Ein wichtiger Hinweis vorweg

Ein QGIS-Task darf niemals GUI-basierte Vorgänge ausführen, beispielsweise das Erstellen neuer Widgets oder die Interaktion mit vorhandenen Widgets. Auf Qt-Widgets darf nur vom Hauptthread aus zugegriffen oder diese geändert werden. Ansonsten kommt es zu Abstürzen von QGIS.

Wie lässt sich ein QGIS-Task erstellen?

Grundsätzlich gibt es drei Möglichkeiten, einen QGIS-Task zu erstellen:

  1. Aus einer Funktion

  2. Durch Erweiterung der QgsTask-Klasse

  3. Aus einem Processing-Algorithmus

Nachfolgend werden Code-Auszüge aus dem vorangehend erwähnten Plugin zur Datenaktualisierung am Beispiel des zu aktualisierenden Vektorlayers „Geographische Namen“ gezeigt, in welchem ein QGIS-Task aus einer Funktion verwendet wird (d.h. Möglichkeit 1).

Die anderen beiden Möglichkeiten zur Erstellung eines QGIS-Tasks werden im PyQGIS Developer Cookbook näher beschrieben.

Für unseren Task verwenden wir zum einen diese Funktion:

def run_update_gn_task(self, task, dirname, overwrite):
    QgsMessageLog.logMessage(...)
    
    task.setProgress(0)

    update_gn_task(task, dirname, overwrite)

    if task.isCanceled():
        self.stopped(task)
        return None

    task.setProgress(100)
    
    return {'task': task.description()}
  • Die Funktion run_update_gn_task repräsentiert den Task und kümmert sich u.a. darum, dass die Ausführung in der GUI abgebrochen werden kann. Die eigentliche Arbeit (hier nicht weiter relevant) wird von der update_gn_task-Funktion durchgeführt. In unserem Fall wird bei erfolgreicher Beendigung der Aufgabe nur beispielhaft die Beschreibung des Tasks zurückgegeben.
  • In diesem Beispiel verfügt die Funktion über zwei zusätzliche Parameter, dirname und overwrite, welche für die Ausführung der update_gn_task-Funktion nötig sind.
  • Lang laufende Tasks sollten regelmäßig task.isCanceled() überprüfen, um festzustellen, ob der Task eventuell abgebrochen wurde

Zwei weitere Funktionen benötigen wir noch, um einen Task vollständig definieren zu können:

def stopped(self, task):
    QgsMessageLog.logMessage(...)

def completed(self, exception, result=None):
    if exception is None:
        if result is None:
            QgsMessageLog.logMessage(...)
        else:
            QgsMessageLog.logMessage(...)
    else:
        QgsMessageLog.logMessage(...)
        raise exception
  • Die Funktion stopped nutzen wir im Falle, dass die Ausführung des Tasks durch die Nutzerin abgebrochen wurde
  • Die Funktion completed wird aufgerufen, wenn run_update_gn_task abgeschlossen ist
  • Der Rückgabewert von run_update_gn_task wird vom Task-Manager (s. u.) automatisch als result-Parameter übergeben
  • Sollte run_update_gn_task eine Exception ausgelöst haben, wird diese als exception-Parameter übergeben

Wie lassen sich die einzelnen Tasks steuern?

Mit QgsTask.fromFunction können wir die Task-Funktion und die completed-Funktion nun zu einem QgsTask umwandeln:

def run(self):
    
    ...
    
    self.task = QgsTask.fromFunction("Aktualisierung von Geographische Namen",
                                        self.run_update_gn_task, on_finished=self.completed, dirname=dirname,
                                        overwrite=overwrite, flags=QgsTask.CanCancel)
    
    QgsApplication.taskManager().addTask(self.task)

Sobald ein Task erstellt wurde, kann er mit der Funktion addTask() des Task-Managers zur Ausführung geplant werden.

Das „Eigentum“ am Task wird durch das Hinzufügen zum Task-Manager an letzteren übertragen. Der Task-Manager ist dann für den Start des Tasks verantwortlich. Außerdem bereinigt und löscht er Tasks, nachdem sie ausgeführt wurden.

Der Status von Tasks kann mithilfe von verschiedenen Signalen und Funktionen überwacht werden.

Mein persönliches Fazit zu QGIS Tasks

Wenn Sie große Datensätze herunterladen oder umfangreiche Prozesse durchführen möchten – wie dies im Falle unseres Plugins der Fall war – sind QGIS-Tasks eine gute Möglichkeit, eine reaktionsschnelle Benutzeroberfläche beizubehalten. Unbedingt zu beachten ist allerdings, dass man in einem QGIS-Task keinesfalls GUI-basierte Vorgänge ausführen darf, da es sonst zu unerwarteten Abstürzen von QGIS kommt – welche ich mir anfangs, zugegebenermaßen nicht richtig erklären konnte. Dies hat bei mir zwar ein gewisses Umdenken in der Plugin-Programmierung erfordert, aber schließlich ermöglichen mir QGIS-Tasks, große Datensätze herunterzuladen und währenddessen trotzdem normal in QGIS weiterzuarbeiten.

Weitere Beiträge, die Dich interessieren könnten:

Dr. Christoph Welker

Christoph Welker ist promovierter Meteorologe, in Hamburg zu Hause und seit Mai 2022 als GIS-Consultant bei der WhereGroup tätig. Er hat sich in den letzten Jahren auf die Verarbeitung und Visualisierung von Geodaten basierend auf freier Software spezialisiert.

Artikel teilen: