8

I'm trying to develop a plugin, which does heavy computations and as a result, user interface becomes inactive. I know that I should use threads to keep the UI alive (specifically, I have a progress bar that I want to update), but my experience with threads is very limited.

I was wondering if there are any good tutorials, or code snippets with documentation that cover this issue. sample of my code so far (without threads):

    def initGui(self):
    # Create action that will start plugin configuration
    self.action = QAction(
        QIcon(":/plugins/lts/icon.png"),
        u"LTS calculator", self.iface.mainWindow())
    # connect the action to the run method
    self.action.triggered.connect(self.run)

    # Add toolbar button and menu item
    self.iface.addToolBarIcon(self.action)
    self.iface.addPluginToMenu(u"&LTS Calculator", self.action)

    # QtCore.QObject.connect(self.dlg.ui.find_cc, QtCore.SIGNAL("clicked()"), self.find_connected_components)

    QtCore.QObject.connect(self.dlg.ui.process_Button,QtCore.SIGNAL("clicked()"), self.process)
    # QtCore.QObject.connect(self.dlg.ui.layerCombo,QtCore.SIGNAL("currentIndexChanged(int)"), self.update_lts_field)
    QtCore.QObject.connect(self.dlg.ui.layerCombo,QtCore.SIGNAL("activated (int)"), self.update_lts_field)
    QtCore.QObject.connect(self.dlg.ui.find_cc_Button,QtCore.SIGNAL("clicked()"), self.find_connected_components)


    self.update_ui()
    self.layers = self.iface.legendInterface().layers()  # store the layer list 
    # self.dlg.ui.layerCombo.clear()  # clear the combo 
    for layer in self.layers:    # foreach layer in legend 
        self.dlg.ui.layerCombo.addItem( layer.name() )    # add it t


   def process(self):
    """ Calculates Stress for the selected layer"""
    index = self.dlg.ui.layerCombo.currentIndex() 
    if index < 0: 
        # it may occur if there's no layer in the combo/legend 
        pass
    else: 
        layer = self.dlg.ui.layerCombo.itemData(index) 
    # layer = QgsVectorLayer(self.fileName, "layer_name", "ogr")
    nFeat = layer.featureCount()
    layer.startEditing()
    ...
    street.compute_LTS()
        if street.LTS != None :
            i+=1
            j=ceil(i/(nFeat/100))
            self.dlg.ui.progress_bar.setValue(j)
            layer.updateFeature(feature)
# run method that performs all the real work
def run(self):
    # show the dialog
    self.dlg.show()
    self.dlg.ui.progress_bar.setValue(0)
    self.dlg.ui.progressBar.setValue(0)
    self.dlg.ui.layerCombo.clear()
    self.dlg.ui.lts_combo.clear()

    layers = QgsMapLayerRegistry.instance().mapLayers().values()
    for layer in layers:
        if layer.type() == QgsMapLayer.VectorLayer and layer.geometryType() == QGis.Line:
            self.dlg.ui.layerCombo.addItem( layer.name(), layer ) 

    self.update_lts_field()
    # Run the dialog event loop
    result = self.dlg.exec_()
    # See if OK was pressed
    if result == 1:
        # do something useful (delete the line containing pass and
        # substitute with your code)
        pass

Thanks

3 Answers 3

6

Use processing in QGIS 3.x

In QGIS 3.x there is an infrastructure in place to have things running in threads. Processing is a good candidate to be what you are looking for when doing "heavy computation" things. If you implement your algorithm as a processing algorithm, it will also be reusable by other code of yours.

Have a look at one of the many algorithms which are shipped with QGIS already.

Using the task manager in QGIS 3.x

If processing is no suitable solution for a problem, it's also easy to spawn one (or more) background tasks.

An example for doing this can be found in the QGIS repository

from time import sleep
class TestTask( QgsTask ):

    def __init__(self, desc, time ):
        QgsTask.__init__(self, desc )
        self.time= time

    def run(self):
        wait_time = self.time / 100.0
        for i in range(101):
            sleep(wait_time)
            self.setProgress(i)
            if self.isCancelled():
                self.stopped()
                return 
        self.completed()

for time in range(10, 14):
    task = TestTask('wait {}'.format( time ), time ) 
    QgsApplication.taskManager().addTask( task )

Using threads in QGIS 2.x

For QGIS 2.x, more manual work is required, but background tasks can also be implemented.

http://www.opengis.ch/2016/09/07/using-threads-in-qgis-python-plugins/

2
  • for work in QGIS 3.x need replace QgsTaskManager.instance().addTask( task ) for QgsApplication.taskManager().addTask( task )
    – Fran Raga
    Commented Nov 24, 2017 at 13:28
  • Thanks, the code before was just development code and never made into any release. It's very likely that this code as proposed by you will make it into the final release. Commented Nov 24, 2017 at 16:17
3

Not sure if this applies to your situation, but rather than threads you could throw up a QProgressDialog to show that things are still happening, along with a wait cursor (QApplication.setOverrideCursor).

You can use QApplication.processEvents() to make the UI appear to be alive while the processing is taking place.

If you want the user to be able to interact with the UI while the processing is taking place, that's another matter.

Also take a look at this question: How do I maintain a resposive GUI using QThread with PyQGIS

3
  • Thanks! I have a progress bar that I need to update while the calculations are going on. Can I do this with QApplication.processEvents() or methods like that?
    – Alex
    Commented Apr 29, 2014 at 18:14
  • 1
    processEvents just allows all pending UI events to be processed and the display updated. You can use a QTimer in conjunction with processEvents to keep the progress bar moving. See the description of QTimer: qt-project.org/doc/qt-4.7/qtimer.html
    – gsherman
    Commented Apr 29, 2014 at 18:37
  • UsingprocessEvents is dangerous and discouraged, see this answer for more details. Commented Oct 26, 2017 at 7:09
-3

I used to suffer from this problem and didn`t find any simple way to solve it. At the end, I find a way to relieve it. Just open the python console and add the print code in during the loop. It might keep the UI alive(similar to force the QGIS software UI to refresh itself)

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.