7

I'm using QThread to run processingBar and heavy job so the user will know it's loading. Everything works fine until the job is done then the map is stuck and not reloading. It looks like I need to reload Qgis somehow so the map that i use will work well after the thread is done

This is what I tried:

def onStart(self):
    self.dlg.progressBar.setRange(0, 0)
    self.myLongTask.start()

def onFinished(self):
    self.dlg.progressBar.setRange(0, 1)

def run(self):
        self.dlg.progressBar.setRange(0, 1)
        self.dlg.pushButton.clicked.connect(self.onStart)
        self.myLongTask = workThread()
        self.myLongTask.taskFinished.connect(self.onFinished)



class workThread(QtCore.QThread):
   taskFinished = QtCore.pyqtSignal()
    def run(self):
        time.sleep(3)
        self.taskFinished.emit()

Update: I try using QObject as showed here:https://realpython.com/python-pyqt-qthread/

This is how my run look like:

        self.dlg.progressBar.setRange(0, 1)

        self.thread=QThread()
        self.indexThread=IndexThread()
        self.indexThread.moveToThread(self.thread)
        self.thread.started.connect(self.indexThread.run)
        self.indexThread.taskFinished.connect(self.thread.quit)
        self.indexThread.taskFinished.connect(self.indexThread.deleteLater)
        self.indexThread.taskFinished.connect(self.onFinished)

        self.thread.finished.connect(self.thread.deleteLater)

        self.dlg.indexButton.clicked.connect(self.onStart)

On start:

def onStart(self):
    print("test")
    self.thread.start()
    self.dlg.progressBar.setRange(0, 0)

On finished:

def onFinished(self):
    print("finishing")
    self.dlg.progressBar.setRange(0, 1)

and I get the same result. Everything works but the used map on QGIS won't reload and get stuck. Any idea?

8

2 Answers 2

5

Here is a working example of using Qthread inside a QGIS plugin. This is a minimal plugin based on the example from Martin Dobias here, and implements a trivial example of QThread based on the examples here and here. I believe that the more recent improved examples of QThread implementation are thanks to the work of Maya Posch. You can save the two files below (__init__.py and metadata.txt) into a folder and copy to your QGIS plugins folder to install and test.

from PyQt5.QtCore import QThread, QObject, pyqtSignal

from PyQt5.QtWidgets import (QAction, QMessageBox, QDialog, QVBoxLayout,
                            QLabel, QLineEdit, QProgressBar, QDialogButtonBox,
                            )

import time

def classFactory(iface):
    return QThreadExample(iface)


class QThreadExample:
    def __init__(self, iface):
        self.iface = iface
        self.dlg = testDialog()
        
        self.thread = None
        self.worker = None

    def initGui(self):
        self.action = QAction('QTE!', self.iface.mainWindow())
        self.action.triggered.connect(self.run)
        self.iface.addToolBarIcon(self.action)
        
        self.dlg.btns.accepted.connect(self.create_thread)
        self.dlg.btns.rejected.connect(self.kill_thread)

    def unload(self):
        self.iface.removeToolBarIcon(self.action)
        del self.action

    def run(self):
        self.dlg.show()
        self.dlg.prog.setValue(0)
        self.dlg.info.clear()

    def create_thread(self):
        self.thread = QThread()
        self.worker = Worker()
        self.worker.moveToThread(self.thread)
        self.thread.started.connect(self.worker.process)
        self.worker.progressChanged.connect(lambda: self.dlg.prog.setValue(self.worker.progress))
        self.worker.finished.connect(self.worker_finished)
        self.thread.finished.connect(self.thread.deleteLater)
        self.thread.start()
        self.dlg.info.setText('Running')

    def kill_thread(self):
        self.worker.cancel()
        
    def worker_finished(self, result):
        # catch an emitted object (other than bool) if needed        
        self.dlg.prog.setValue(0)
        if result:
            self.dlg.info.setText(result)
        elif not result:
            self.dlg.info.setText('Task was cancelled!')
        self.thread.quit()#IMPORTANT! emits thread.finished signal        
    

###---WORKER CLASS---###
class Worker(QObject):
    progressChanged = pyqtSignal()
    finished = pyqtSignal(object)
    cancelled = pyqtSignal()
    
    def __init__(self): # define additional constructor parameters if required
        QObject.__init__(self)
        self.progress = 0
        self.isCancelled = False
        
    def process(self):
        for i in range(21):
            time.sleep(0.5)
            val = i * 5
            self.setProgress(val)
            if self.isCancelled:
                self.finished.emit(False)
                return
                
        self.finished.emit('Task finished') # emit an object if required
        
    def setProgress(self, progressValue):
        self.progress = progressValue
        self.progressChanged.emit()
        
    def cancel(self):
        self.isCancelled = True
        self.cancelled.emit()


###---PLUGIN DIALOG CLASS---###
class testDialog(QDialog):
    
    def __init__(self):
        QDialog.__init__(self)
        self.setGeometry(200, 200, 500, 350)
        layout = QVBoxLayout()
        self.lbl_1 = QLabel('Info: ', self)
        self.info = QLineEdit(self)
        self.lbl_2 = QLabel('Progress: ', self)
        self.prog = QProgressBar(self)
        self.btns = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel, self)
        for c in self.children():
            layout.addWidget(c)
        self.setLayout(layout)

metadata.txt

[general]
name=QThreadExample
description=QThread example plugin
about=A trivial example of using QThread in a plugin
version=1.0
qgisMinimumVersion=3.0
author=Your Name
email=your.name@gmail.com
repository=URL to the code repository

You can see from the screencasts below that the implementation of Qthread inside the plugin works fine and that the map canvas remains responsive after the completion of the background thread.

enter image description here

enter image description here

2
  • Helped a lot! Thank you
    – Micha
    Jan 5, 2022 at 12:16
  • @Micha, you're welcome, glad it helped!
    – Ben W
    Jan 5, 2022 at 12:42
0

For anyone else who struggling with it. After looking at @Ben W answer make sure that everything that is UI is separated from the Worker thread. Not a single iface should be on the worker thread.

Your Answer

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

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