3

From my own experience and questions and answers here about QgsTask I understand I simply can't interact with GUI (widgets) from QgsTask in any way (logMessage seems to be an exception) as it freezes QGIS. In particular I would like to use pushMessage, in general turn on/off buttons etc. depending on how is subtask progressing.

Is there a way to push message from subtask? I would expect somehow flag to main process to push message from there? Just can't find any good example of this.

2
  • 1
    Have you looked at using the feedback class? Commented Apr 1, 2021 at 7:23
  • @firefly-orange Thank you, that looks exactly like something I am after, thank you for the tip.
    – Miro
    Commented Apr 1, 2021 at 12:02

1 Answer 1

5

The approach I would suggest would be using custom PyQt signals. The example below is based on the example for extending QgsTask in the PyQGIS Developer Cookbook, modified to emit custom signals from the background task and listen for them from the main thread. You can then connect to a slot which pushes a message to the message bar or otherwise interacts with interface/gui elements and widgets.

import random
from time import sleep

MESSAGE_CATEGORY = 'RandomIntegerSumTask'

class RandomIntegerSumTask(QgsTask):
    # declare custom signals
    half_finished = pyqtSignal(str)
    number_found = pyqtSignal()

    def __init__(self, description, duration):
        super().__init__(description, QgsTask.CanCancel)
        self.description = description
        self.duration = duration
        self.iterations = 0

    def run(self):
        QgsMessageLog.logMessage('Started task "{}"'.format(
                                     self.description),
                                 MESSAGE_CATEGORY, Qgis.Info)
        wait_time = self.duration / 50
        for i in range(20):
            # report progress
            self.setProgress(i*5)
            if i*5 == 50:
                # emit custom signal at 50% task completion
                self.half_finished.emit(self.description)
            arandominteger = random.randint(0, 100)
            self.iterations += 1
            # check isCanceled() to handle cancellation
            if self.isCanceled():
                return False
            if arandominteger == 42:
                self.number_found.emit()
                super().cancel()
            sleep(wait_time)
        return True

    def finished(self, result):
        if result:
            QgsMessageLog.logMessage(
                'RandomTask {} completed with {} iterations'.format(
                  self.description, self.iterations), MESSAGE_CATEGORY, Qgis.Success)
        else:
            QgsMessageLog.logMessage(
                'RandomTask {} did not complete successfully- '\
                'encountered random integer 42!)'.format(
                    self.description),
                MESSAGE_CATEGORY, Qgis.Warning)


class main(QWidget):
    
    def __init__(self):
        QWidget.__init__(self)
        layout = QVBoxLayout()
        self.lbl1 = QLabel('Task Progress:', self)
        self.prog1 = QProgressBar(self)
        self.lbl2 = QLabel('Sub-Task Progress:', self)
        self.prog2 = QProgressBar(self)
        for c in self.children():
            layout.addWidget(c)
        self.setLayout(layout)
        self.task = RandomIntegerSumTask('waste cpu', 10)
        self.task.progressChanged.connect(lambda: self.prog1.setValue(self.task.progress()))
#        self.task.half_finished.connect(self.push_message_halfway)
        # listen for custom signal and connect to a slot
        self.task.number_found.connect(self.push_message_number_found)
        self.subtask = RandomIntegerSumTask('waste cpu subtask short', 5)
        self.subtask.progressChanged.connect(lambda: self.prog2.setValue(self.subtask.progress()))
        # custom signals
        self.subtask.half_finished.connect(self.push_message_halfway)
        self.subtask.number_found.connect(self.push_message_number_found)
        # Add a subtask
        self.task.addSubTask(self.subtask)
        QgsApplication.taskManager().addTask(self.task)
        self.show()

    def push_message_halfway(self, name):
        iface.messageBar().pushMessage('Task {} is halfway through'.format(name))
        
    def push_message_number_found(self):
        iface.messageBar().pushMessage('Number 42 was generated- task cancelled!')


main()
1

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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