4

I'm rewriting a plugin to use QgsTask with this as my first guideline and the update mentioned in this post as a possible solution for my problem. My task runs fine in the background, but

def finished(self, result):

and

def completed(self, exception, result=None):

are never called. Anybody any ideas?

EDIT: added more code below
EDIT 2: found out that if there is an error in the method run it then actually reaches finished before it gives an error.

main_plugin.py

# Import link to Qt and QGIS
from qgis.PyQt.QtGui import *
from qgis.PyQt.QtCore import *
from qgis.PyQt.QtWidgets import QAction, QMenu, QFileDialog
from qgis.core import *
from qgis.gui import QgsMessageBar

# Import everything to create the plugin
from . import resources
from .plugin_dialog import PluginDialog
from .gdal_raster import Raster
from .task_raster import CreateRasterTask
from .task_raster import DoStuff

# Import everything to run the plugin
import sys
import os


class RasterPreparePlugin(object):
    def __init__(self, iface):
        # save reference to the QGIS interface
        self.iface = iface

    def run(self):
        self.dlg = PluginDialog()
        self.dlg.show()

        # Run the dialog event loop
        result = self.dlg.exec_()

        if result:
            layer = combobox.currentLayer()
            path = self.dlg.ui.LineEditOutputLayer.text()

            raster = Raster(layer, path, VERSION)

            okay = DoStuff(raster, self.iface)
            okay.do_subclassed_tasks()

task_raster.py

from qgis.core import (
    QgsApplication,
    QgsTask,
    QgsMessageLog,
    Qgis,
    )

MESSAGE_CATEGORY = 'CreateRasterTask'

class CreateRasterTask(QgsTask):
    def __init__(self, description, raster):
        super(QgsTask, self).__init__(description, QgsTask.CanCancel)
        self.exception = None
        self.raster = raster

    def run(self):
        test = self.raster.task()

        return test

    def finished(self, result):
        if result:
            QgsMessageLog.logMessage(
                'Task "{name}" completed'.format(
                name=self.description()
                ),
            MESSAGE_CATEGORY, Qgis.Success)
        else:
            if self.exception is None:
                QgsMessageLog.logMessage(
                    'Task "{name}" not successful but without '\
                    'exception (probably the task was manually '\
                    'canceled by the user)'.format(
                        name=self.description()),
                    MESSAGE_CATEGORY, Qgis.Warning)
            else:
                QgsMessageLog.logMessage(
                    'Task "{name}" Exception: {exception}'.format(
                        name=self.description(),
                        exception=self.exception),
                    MESSAGE_CATEGORY, Qgis.Critical)
                raise self.exception

    def cancel(self):
        QgsMessageLog.logMessage(
            'Task "{name}" was canceled'.format(
                name=self.description()),
            MESSAGE_CATEGORY, Qgis.Info)
        super().cancel()


class DoStuff:
    def __init__(self, raster, iface):
        self.raster = raster
        self.iface = iface
        self.task = CreateRasterTask('Test', self.raster)

    def do_subclassed_tasks(self):
        """Do tasks using QgsTask subclass. """
        QgsApplication.taskManager().addTask(self.task)

    def stopped(self, task):
        QgsMessageLog.logMessage(
            'Task "{name}" was canceled'.format(
                name=task.description()),
            self.MESSAGE_CATEGORY, Qgis.Info)

    def completed(self, exception, result=None):
        if exception is None:
            if result is None:
                QgsMessageLog.logMessage(
                    'Completed with no exception and no result '\
                    '(probably manually canceled by the user)',
                    MESSAGE_CATEGORY, Qgis.Warning)
            else:
                QgsMessageLog.logMessage(
                    'Task "{name}" finished'.format(
                        name=task.description()),
                    self.MESSAGE_CATEGORY, Qgis.Info)
        else:
            QgsMessageLog.logMessage("Exception: {}".format(exception),
                                    MESSAGE_CATEGORY, Qgis.Critical)
            raise exception

gdal_raster.py

# Import link to QGIS
from qgis.core import (
    QgsPointXY,
    QgsExpressionContextScope,
    QgsExpressionContextUtils,
    QgsMessageLog,
    Qgis,
    )

# Import everything to run the plugin
import osgeo.gdal as gdal
import osgeo.osr as osr
import numpy as np
import datetime
import math


class Raster(object):
    def __init__(self, layer, path, version):
        self.layer = layer
        self.path = path
        self.version = version

    def task(self):
        QgsMessageLog.logMessage('Debug 5', 'MESSAGE_CATEGORY', Qgis.Success)
        try:
            # do stuff
            return True
        except:
            return False
10
  • Maybe you can share a little bit more of your code to help us understand what could be wrong...
    – YoLecomte
    Apr 7, 2020 at 19:02
  • @YoLecomte of course. Removed non essential stuff, but added the three files I use.
    – Matthieu
    Apr 7, 2020 at 20:20
  • In your example DoStuff.do_subclassed_tasks is never called. So your task is not executed (or I miss something).
    – YoLecomte
    Apr 7, 2020 at 21:08
  • It is called, missed to copy that line. So the method 'run' is executed, but it never triggers the method 'finished'.
    – Matthieu
    Apr 8, 2020 at 7:30
  • @YoLecomte any ideas? I added some extra information I found.
    – Matthieu
    Apr 9, 2020 at 8:13

1 Answer 1

2

I've had the same trouble with QgsTask in the past. The issue (as far as I can work out) seems to be when tasks are run from a plugin. I've tried both tasks from a function and subclassed tasks from the python console and they both called the finished function upon completion.

As a workaround for subclassed tasks in a plugin I was using pyqtSignal in the tasks class which I would subscribe to in the main thread. I added the myObjectSignal = pyqtSignal(object) at the start of class before its initialized. I'm using an object but you could return any type eg myListSignal = pyqtSignal(list)

class SubclassedTask(QgsTask):

    myObjectSignal = pyqtSignal(object)

    def __init__(self, argument1):
        super().__init__(_description, QgsTask.CanCancel)
        self.argument1 = argument1
        self.result = None

In the run method of the class I would emit the signal if it reached the end

     def run(self):
         Caluclate the result...
         self.myObjectSignal.emit(self.result)
         return True

In the main thread I would create the class instance then subscribe to the signal in it before adding it to the task manager. This would connect to a function in the main thread that could do something with the information being returned.

        newTask = SubclassedTask(argument1)
        newTask.myObjectSignal.connect(self.completeTaskProcessing)
        QgsApplication.taskManager().addTask(newTask)

The handling method in the main thread would look like this

     def completeTaskProcessing(self,taskResult):

         print(taskResult)

I probably should have been disconnecting from the signal upon completion but I never added it and it seems to work currently so I'm not looking to touch it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.