{"id":1668,"date":"2016-09-07T09:38:15","date_gmt":"2016-09-07T07:38:15","guid":{"rendered":"https:\/\/www.opengis.ch\/?p=1668"},"modified":"2020-04-29T16:05:13","modified_gmt":"2020-04-29T14:05:13","slug":"using-threads-in-qgis-python-plugins","status":"publish","type":"post","link":"https:\/\/www.opengis.ch\/fr\/2016\/09\/07\/using-threads-in-qgis-python-plugins\/","title":{"rendered":"Using threads in QGIS python plugins"},"content":{"rendered":"<p>I really wanted to write this post since a long time but things got in the way, and now an email finally triggered me.<br \/>\nAs part of a consultancy I got to work with threads in a python plugin for QGIS. Since it was a pretty tedious process, I decided to write \u00a0the whole thing fairly generic so that it could be used easily by others.<br \/>\nBefore using this, please note that there are ongoing efforts to get something like this <a href=\"https:\/\/github.com\/qgis\/QGIS\/pull\/3004\">directly in QGIS 3<\/a>.<br \/>\nThe code below (or maybe a more recent version &#8211; the one posted here is\u00a0<a class=\"commit-tease-sha\" href=\"https:\/\/github.com\/mbernasocchi\/pyqtExperiments\/commit\/1da300f1297104f54aa4865591147a4308d8a301\" data-pjax=\"\">1da300f<\/a> from<relative-time datetime=\"2015-05-06T13:26:59Z\" title=\"May 6, 2015, 3:26 PM GMT+2\">\u00a0May 6, 2015<\/relative-time>) can be found on\u00a0<a href=\"https:\/\/github.com\/mbernasocchi\/pyqtExperiments\/blob\/master\/qgis_thread_example.py\">github<\/a><br \/>\nThe usage is pretty self explanatory but it goes like this.\u00a0You create your worker class that inherits from\u00a0AbstractWorker and implement the work method:<\/p>\n<pre class=\"lang:python decode:true\" title=\"Subclass AbstractWorker\">class ExampleWorker(AbstractWorker):\n    \"\"\"worker, implement the work method here and raise exceptions if needed\"\"\"\n    def __init__(self, steps):\n        AbstractWorker.__init__(self)\n        self.steps = steps\n        # if a worker cannot define the length of the work it can set an\n        # undefined progress by using\n        # self.toggle_show_progress.emit(False)\n    def work(self):\n        print('Doing some long running job')<\/pre>\n<p>in your code you import all the needed modules and functions and call your ExampleWorker like this:<\/p>\n<pre class=\"lang:python decode:true\" title=\"Call your worker\">def run_example_worker():\n    # create a new worker instance that does 7 steps\n    worker = ExampleWorker(7)\n    start_worker(worker, self.iface, 'testing the worker')<\/pre>\n<p>here, for reference, the whole code<\/p>\n<pre class=\"lang:python decode:true\" title=\"Whole code\"># -*- coding: utf-8 -*-\n\"\"\"\n    This program is free software: you can redistribute it and\/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see &lt;https:\/\/www.gnu.org\/licenses\/&gt;.\n    this was inspired by:\n    https:\/\/snorf.net\/blog\/2013\/12\/07\/multithreading-in-qgis-python-plugins\/\n    https:\/\/eli.thegreenplace.net\/2011\/04\/25\/passing-extra-arguments-to-pyqt-slot\n    https:\/\/gis.stackexchange.com\/questions\/64831\/how-do-i-prevent-qgis-from-being-detected-as-not-responding-when-running-a-hea\/64928#64928\n\"\"\"\n__author__ = 'marco@opengis.ch'\nimport time\nimport traceback\nfrom random import randint\nfrom PyQt4 import QtCore\nfrom PyQt4.QtCore import QThread, Qt\nfrom PyQt4.QtGui import QProgressBar, QPushButton\nfrom qgis.core import QgsMessageLog\nfrom qgis.gui import QgsMessageBar\n###########################################################################\n# This is what you need to call when you want to start a work in a thread #\n###########################################################################\ndef run_example_worker():\n    # create a new worker instance that does 7 steps\n    worker = ExampleWorker(7)\n    start_worker(worker, self.iface, 'testing the worker')\n###########################################################################\n# This could be in a separate file example_worker.py                      #\n###########################################################################\nclass ExampleWorker(AbstractWorker):\n    \"\"\"worker, implement the work method here and raise exceptions if needed\"\"\"\n    def __init__(self, steps):\n        AbstractWorker.__init__(self)\n        self.steps = steps\n        # if a worker cannot define the length of the work it can set an\n        # undefined progress by using\n        # self.toggle_show_progress.emit(False)\n    def work(self):\n        if randint(0, 100) &gt; 70:\n            raise RuntimeError('This is a random mistake during the '\n                               'calculation')\n        self.toggle_show_progress.emit(False)\n        self.toggle_show_cancel.emit(False)\n        self.set_message.emit(\n            'NOT showing the progress because we dont know the length')\n        sleep(randint(0, 10))\n        self.toggle_show_cancel.emit(True)\n        self.toggle_show_progress.emit(True)\n        self.set_message.emit(\n            'Doing long running job while showing the progress')\n        for i in range(1, self.steps+1):\n            if self.killed:\n                self.cleanup()\n                raise UserAbortedNotification('USER Killed')\n            # wait one second\n            time.sleep(1)\n            self.progress.emit(i * 100\/self.steps)\n        return True\n    def cleanup(self):\n        print \"cleanup here\"\n###########################################################################\n# This could be in a separate file abstract_worker.py                     #\n###########################################################################\nclass AbstractWorker(QtCore.QObject):\n    \"\"\"Abstract worker, ihnerit from this and implement the work method\"\"\"\n    # available signals to be used in the concrete worker\n    finished = QtCore.pyqtSignal(object)\n    error = QtCore.pyqtSignal(Exception, basestring)\n    progress = QtCore.pyqtSignal(float)\n    toggle_show_progress = QtCore.pyqtSignal(bool)\n    set_message = QtCore.pyqtSignal(str)\n    toggle_show_cancel = QtCore.pyqtSignal(bool)\n    # private signal, don't use in concrete workers this is automatically\n    # emitted if the result is not None\n    successfully_finished = QtCore.pyqtSignal(object)\n    def __init__(self):\n        QtCore.QObject.__init__(self)\n        self.killed = False\n    def run(self):\n        try:\n            result = self.work()\n            self.finished.emit(result)\n        except UserAbortedNotification:\n            self.finished.emit(None)\n        except Exception, e:\n            # forward the exception upstream\n            self.error.emit(e, traceback.format_exc())\n            self.finished.emit(None)\n    def work(self):\n        \"\"\" Reimplement this putting your calculation here\n            available are:\n                self.progress.emit(0-100)\n                self.killed\n            :returns a python object - use None if killed is true\n        \"\"\"\n        raise NotImplementedError\n    def kill(self):\n        self.is_killed = True\n        self.set_message.emit('Aborting...')\n        self.toggle_show_progress.emit(False)\nclass UserAbortedNotification(Exception):\n    pass\ndef start_worker(worker, iface, message, with_progress=True):\n    # configure the QgsMessageBar\n    message_bar_item = iface.messageBar().createMessage(message)\n    progress_bar = QProgressBar()\n    progress_bar.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)\n    if not with_progress:\n        progress_bar.setMinimum(0)\n        progress_bar.setMaximum(0)\n    cancel_button = QPushButton()\n    cancel_button.setText('Cancel')\n    cancel_button.clicked.connect(worker.kill)\n    message_bar_item.layout().addWidget(progress_bar)\n    message_bar_item.layout().addWidget(cancel_button)\n    iface.messageBar().pushWidget(message_bar_item, iface.messageBar().INFO)\n    # start the worker in a new thread\n    # let Qt take ownership of the QThread\n    thread = QThread(iface.mainWindow())\n    worker.moveToThread(thread)\n    worker.set_message.connect(lambda message: set_worker_message(\n        message, message_bar_item))\n    worker.toggle_show_progress.connect(lambda show: toggle_worker_progress(\n        show, progress_bar))\n    worker.toggle_show_cancel.connect(lambda show: toggle_worker_cancel(\n        show, cancel_button))\n    worker.finished.connect(lambda result: worker_finished(\n        result, thread, worker, iface, message_bar_item))\n    worker.error.connect(lambda e, exception_str: worker_error(\n        e, exception_str, iface))\n    worker.progress.connect(progress_bar.setValue)\n    thread.started.connect(worker.run)\n    thread.start()\n    return thread, message_bar_item\ndef worker_finished(result, thread, worker, iface, message_bar_item):\n        # remove widget from message bar\n        iface.messageBar().popWidget(message_bar_item)\n        if result is not None:\n            # report the result\n            iface.messageBar().pushMessage('The result is: %s.' % result)\n            worker.successfully_finished.emit(result)\n        # clean up the worker and thread\n        worker.deleteLater()\n        thread.quit()\n        thread.wait()\n        thread.deleteLater()\ndef worker_error(e, exception_string, iface):\n    # notify the user that something went wrong\n    iface.messageBar().pushMessage(\n        'Something went wrong! See the message log for more information.',\n        level=QgsMessageBar.CRITICAL,\n        duration=3)\n    QgsMessageLog.logMessage(\n        'Worker thread raised an exception: %s' % exception_string,\n        'SVIR worker',\n        level=QgsMessageLog.CRITICAL)\ndef set_worker_message(message, message_bar_item):\n    message_bar_item.setText(message)\ndef toggle_worker_progress(show_progress, progress_bar):\n    progress_bar.setMinimum(0)\n    if show_progress:\n        progress_bar.setMaximum(100)\n    else:\n        # show an undefined progress\n        progress_bar.setMaximum(0)\ndef toggle_worker_cancel(show_cancel, cancel_button):\n    cancel_button.setVisible(show_cancel)\n<\/pre>\n<p>Hope this could help you, if you need professional\u00a0help don&rsquo;t hesitate <a href=\"https:\/\/www.opengis.ch\/contact\/\">contacting us<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Here an example on how to work with threads in a consistent and clean manner in QGIS python plugins<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_themeisle_gutenberg_block_has_review":false,"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":true,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[14,15,16],"tags":[125],"class_list":["post-1668","post","type-post","status-publish","format-standard","hentry","category-python","category-qgis","category-qgis-plugins","tag-qgis-org"],"jetpack_featured_media_url":"","jetpack-related-posts":[{"id":154,"url":"https:\/\/www.opengis.ch\/fr\/2010\/12\/06\/qgis-plugins-starter-plugin\/","url_meta":{"origin":1668,"position":0},"title":"Qgis plugins starter plugin","author":"Marco Bernasocchi","date":"6 d\u00e9cembre 2010","format":false,"excerpt":"Today I published my first QGis Python plugin. It does allow to configure a list of available plugins actions to execute in one click. It is published in pyqgis contributed repository and the source is developed on My GitHub Cheers Marco","rel":"","context":"Dans &quot;GIS&quot;","block_context":{"text":"GIS","link":"https:\/\/www.opengis.ch\/fr\/category\/gis\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":3691,"url":"https:\/\/www.opengis.ch\/fr\/2018\/04\/13\/porting-qgis-plugins-to-api-v3-strategy-and-tools\/","url_meta":{"origin":1668,"position":1},"title":"Porting QGIS plugins to API v3 &#8211; Strategy and tools","author":"Marco Bernasocchi","date":"13 avril 2018","format":false,"excerpt":"The Release of QGIS 3.0 was a great success and with the first LTR (3.4) scheduled for release this fall, it is now the perfect time to port your plugins to the new API. QGIS 3.0 is the first major release since September 2013 when QGIS 2.0 was released. During\u2026","rel":"","context":"Dans &quot;Featured&quot;","block_context":{"text":"Featured","link":"https:\/\/www.opengis.ch\/fr\/category\/featured\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":2059,"url":"https:\/\/www.opengis.ch\/fr\/2016\/03\/23\/prepare-your-plugins-for-qgis-3\/","url_meta":{"origin":1668,"position":2},"title":"Prepare your plugins for QGIS 3","author":"Matthias Kuhn","date":"23 mars 2016","format":false,"excerpt":"QGIS 3 is not yet there and there is still plenty of time to prepare and migrate. But I thought I would give some advice about things that you can keep in mind while working on your plugins to make your life easier when you will have to actually do\u2026","rel":"","context":"Dans &quot;Uncategorised&quot;","block_context":{"text":"Uncategorised","link":"https:\/\/www.opengis.ch\/fr\/category\/uncategorised\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":2127,"url":"https:\/\/www.opengis.ch\/fr\/2016\/09\/19\/qgis2-compatibility-plugin\/","url_meta":{"origin":1668,"position":3},"title":"QGIS2 compatibility plugin","author":"Marco Bernasocchi","date":"19 septembre 2016","format":false,"excerpt":"Lately I've been spending time porting a bigger plugin from QGIS 2.8 to 3 while maintaining 2.8 compatibility. You can find it at https:\/\/github.com\/opengisch\/qgis2compat\/ and https:\/\/plugins.qgis.org\/plugins\/qgis2compat\/ One code to rule them all. My target was to have to edit the source code as little as possible to simulate a lazy\u2026","rel":"","context":"Dans &quot;PyQt&quot;","block_context":{"text":"PyQt","link":"https:\/\/www.opengis.ch\/fr\/category\/programming\/python\/pyqt\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":147,"url":"https:\/\/www.opengis.ch\/fr\/2010\/12\/01\/qgis-globe-plugin-installer-script\/","url_meta":{"origin":1668,"position":4},"title":"QGis Globe Plugin installer script","author":"Marco Bernasocchi","date":"1 d\u00e9cembre 2010","format":false,"excerpt":"Lately, thanks to ma Master Thesis, I've been co-working on the Globe Plugin for QGis here my install script for a threaded version of QGis with the Globe Plugin. By now the Globe has stereo 3D support, keyboard navigation (try all the num key), mouse navigation, a gui to control\u2026","rel":"","context":"Dans &quot;C++&quot;","block_context":{"text":"C++","link":"https:\/\/www.opengis.ch\/fr\/category\/programming\/cpp\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":432,"url":"https:\/\/www.opengis.ch\/fr\/2012\/03\/30\/qgis-multiview-and-globe-screenshots\/","url_meta":{"origin":1668,"position":5},"title":"QGIS Multiview and globe screenshots","author":"Marco Bernasocchi","date":"30 mars 2012","format":false,"excerpt":"This screenshots have been created using the QGIS with the following plugins: Multitemporal and multivariate data visualisation (https:\/\/hub.qgis.org\/projects\/multiview) Scttergram identify (https:\/\/hub.qgis.org\/projects\/scattergramdentify Globe Plugin","rel":"","context":"Dans &quot;GIS&quot;","block_context":{"text":"GIS","link":"https:\/\/www.opengis.ch\/fr\/category\/gis\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]}],"jetpack_shortlink":"https:\/\/wp.me\/pbdBtI-qU","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/www.opengis.ch\/fr\/wp-json\/wp\/v2\/posts\/1668","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.opengis.ch\/fr\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.opengis.ch\/fr\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.opengis.ch\/fr\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.opengis.ch\/fr\/wp-json\/wp\/v2\/comments?post=1668"}],"version-history":[{"count":1,"href":"https:\/\/www.opengis.ch\/fr\/wp-json\/wp\/v2\/posts\/1668\/revisions"}],"predecessor-version":[{"id":11142,"href":"https:\/\/www.opengis.ch\/fr\/wp-json\/wp\/v2\/posts\/1668\/revisions\/11142"}],"wp:attachment":[{"href":"https:\/\/www.opengis.ch\/fr\/wp-json\/wp\/v2\/media?parent=1668"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.opengis.ch\/fr\/wp-json\/wp\/v2\/categories?post=1668"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.opengis.ch\/fr\/wp-json\/wp\/v2\/tags?post=1668"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}