I am writing a plugin for QGIS. I created a task by subclassing QgsTask
, it's basically copied from the PyQGIS Cookbook and looks like this:
import json
import urllib.request
from qgis.PyQt.QtCore import NULL
from qgis.core import QgsProject, QgsFeature, QgsVectorLayer, QgsTask
from .utils import point2wgs84, message_bar, log_message, LayerNotFoundError
class GeocodeTask(QgsTask):
def __init__(self, only_missing: bool, config, finished_callback):
if only_missing:
super().__init__('Fehlende Adressen bestimmen')
else:
super().__init__('Alle Adressen bestimmen')
self.finished_callback = finished_callback
self.exception = None
self.only_missing = only_missing
self.config = config
# Counters for progress display
self.total_features = 0
self.finished_features = 0
self.failed_features = 0
# DEBUG
self.debug = True
def finished(self, result):
log_message('Aufgabe abgeschlossen', 'info')
if result:
log_message('Die Aufgabe "{}" wurde erfolgreich beendet. Es wurden {} Adressen nicht gefunden'.format(
self.description, str(self.failed_features)), 'success')
message_bar('Die Aufgabe "{}" wurde erfolgreich beendet. Es wurden {} Adressen nicht gefunden'.format(
self.description, str(self.failed_features)), 'success')
else:
if self.exception is None:
log_message(
'Die Aufgabe "{}" wurde nicht erfolgreich, aber ohne Fehlermeldung beendet.\n\
(Wahrscheinlich vom Benutzer abgebrochen)'.format(
self.description), 'warning')
message_bar('Die Aufgabe "{}" wurde wahrscheinlich vom Benutzer abgebrochen.'.format(self.description),
'warning')
else:
log_message(
'Die Aufgabe "{}" wurde mit einem Fehler beendet: {}'.format(self.description, self.exception),
'critical')
message_bar(
'Die Aufgabe "{}" wurde mit einem Fehler beendet: {}'.format(self.description, self.exception),
'critical')
raise self.exception
self.finished_callback()
def geocode_feature(self, layer: QgsVectorLayer, feature: QgsFeature):
if self.only_missing:
# If the address is already set, continue with the next one
if feature['address'] != NULL:
return True
# Get the coordinates of the point
coord = feature.geometry().asPoint()
coord_converted = point2wgs84(coord)
lat = coord_converted[1]
lon = coord_converted[0]
# Send the coordinates to the osm api
url = 'https://nominatim.openstreetmap.org/reverse?format=json&lat={lat}&lon={lon}'.format(lon=lon, lat=lat)
con = urllib.request.urlopen(url)
res = con.read().decode('utf-8')
con.close()
results = json.loads(res)
try:
# If road, house number, postcode and town is set
result = results['address']['road'] + ' ' + str(results['address']['house_number']) + ', ' + \
results['address']['postcode'] + ' ' + results['address']['town']
except KeyError:
try:
# If road, postcode and town is set, but the house number not
result = results['address']['road'] + ', ' + results['address']['postcode'] + ' ' + results['address'][
'town']
except KeyError:
# If no address is found
result = NULL
# Save the address to the feature
feature['address'] = result
layer.updateFeature(feature)
if result is NULL:
return False
else:
return True
def geocode_layer(self, layer: QgsVectorLayer):
# DEBUG
max_features = self.finished_features + 20
layer.startEditing()
for feature in layer.getFeatures():
# Look if the task was cancelled
if self.isCanceled():
break
# If geocode_feature() is true, an address was set or already found
if not self.geocode_feature(layer, feature):
self.failed_features += 1
self.finished_features += 1
# Display progress
self.setProgress(self.finished_features / self.total_features * 100)
# DEBUG
if self.debug and self.finished_features > max_features:
break
layer.commitChanges()
def run(self):
try:
try:
layer_hydranten: QgsVectorLayer = QgsProject.instance().mapLayersByName(self.config.layers.hydranten)[0]
layer_brunnen: QgsVectorLayer = QgsProject.instance().mapLayersByName(self.config.layers.brunnen)[0]
except IndexError:
message_bar('Ein Layer konnte nicht gefunden werden. Bitte überprüfen Sie die Einstellungen.',
level='critical')
self.exception = LayerNotFoundError()
return False
if self.debug:
self.total_features = 40
else:
self.total_features += layer_brunnen.featureCount()
self.total_features += layer_hydranten.featureCount()
self.geocode_layer(layer_brunnen)
# Look if the task was cancelled
if self.isCanceled():
return False
self.geocode_layer(layer_hydranten)
# Look if the task was cancelled
if self.isCanceled():
return False
return True
except Exception as ex:
self.exception = ex
return False
The task runs great, but when it is finished, only the built-in QGIS message comes up that the task has been completed. What I wrote in the finished()
-function is not executed at all. I start the task by pressing a menu entry that executes this piece of code:
self.task_running = True
task = GeocodeTask(False, self.config, self.set_task_running_false)
QgsApplication.taskManager().addTask(task)
message_bar('Task gestartet: Alle Adressen bestimmen')
And this is my QGIS version:
QGIS-Version 3.20.0-Odense
QGIS-Codeversion decaadbb31
Qt-Version 5.15.2
Python-Version 3.9.5
GDAL-Version 3.3.1
PROJ-Version 8.1.0
EPSG-Registraturdatenbankversion v10.027 (2021-06-17)
GEOS-Version 3.9.1-CAPI-1.14.2
SQLite-Version 3.35.2
PDAL-Versio 2.3.0
PostgreSQL-Client-Version 13.0
SpatiaLite-Version 5.0.1
QWT-Version 6.1.3
QScintilla2-Version 2.11.5
OS-Version Windows 10 Version 2009
Active Python-Plugins: firstaid, GeoCoding, loeschwasser, pluginbuilder3, plugin_reloader, db_manager, processing
Best Answer
i found the same question on the right sidebar. (QGIS QgsTaks finished and completed never called)
It says that QGIS tasks in plugins are broken, because the
finished()
andcompleted()
functions are never executed, but there is a way to achieve the same with Qt Signals.I tried this and it worked:
I added a
pyqtSignal
to my task class:Then i added every time, my run function could exit, this lines:
The first parameter shows if the task completed successfully, the second and third parameters are needed for a message and the last parameter contains an exception (if aviable) to trow it in the main thread.
As last, I moved the finished function to the main plugin class and registered it to the signal in the task: