PyQt ProgressBar PyQt ProgressBar multithreading multithreading

PyQt ProgressBar


You need to allow events to be processed whilst the loop is running so that the application can remain responsive.

Even more importantly, for long-running tasks, you need to provide a way for the user to stop the loop once it's started.

One simple way to do this is to start the loop with a timer, and then periodically call qApp.processEvents whilst the loop is running.

Here's a demo script that does that:

import sys, timefrom PyQt4 import QtGui, QtCoreclass ProgressBar(QtGui.QWidget):    def __init__(self, parent=None, total=20):        super(ProgressBar, self).__init__(parent)        self.progressbar = QtGui.QProgressBar()        self.progressbar.setMinimum(1)        self.progressbar.setMaximum(total)        self.button = QtGui.QPushButton('Start')        self.button.clicked.connect(self.handleButton)        main_layout = QtGui.QGridLayout()        main_layout.addWidget(self.button, 0, 0)        main_layout.addWidget(self.progressbar, 0, 1)        self.setLayout(main_layout)        self.setWindowTitle('Progress')        self._active = False    def handleButton(self):        if not self._active:            self._active = True            self.button.setText('Stop')            if self.progressbar.value() == self.progressbar.maximum():                self.progressbar.reset()            QtCore.QTimer.singleShot(0, self.startLoop)        else:            self._active = False    def closeEvent(self, event):        self._active = False    def startLoop(self):        while True:            time.sleep(0.05)            value = self.progressbar.value() + 1            self.progressbar.setValue(value)            QtGui.qApp.processEvents()            if (not self._active or                value >= self.progressbar.maximum()):                break        self.button.setText('Start')        self._active = Falseapp = QtGui.QApplication(sys.argv)bar = ProgressBar(total=101)bar.show()sys.exit(app.exec_())

UPDATE

Assuming that you're using the C implementation of python (i.e. CPython), the solution to this issue depends entirely on the nature of the task(s) that have to run concurrently with the GUI. More fundamentally, it is determined by CPython having a Global Interpreter Lock (GIL).

I am not going to attempt any explanation of CPython's GIL: instead, I will simply recommend watching this excellent PyCon video by Dave Beazley, and leave it at that.


Generally, when trying to run a GUI concurrently with a background task, the first question to ask is: Is the task IO-bound, or CPU-bound?

If it's IO-bound (e.g. accessing the local file-system, downloading from the internet, etc), then the solution is usually quite straightforward, because CPython always releases the GIL for I/O operations. The background task can simply be done asynchronously, or performed by a worker thread, and nothing special needs to be done to keep the GUI responsive.

The main difficulties occur with CPU-bound tasks, when there is a second question to ask: Can the task be broken down into a series of small steps?

If it can, then the solution is to periodically send requests to the GUI thread to process its current stack of pending events. The demo script above is a crude example of this technique. More usually, the task would be carried out in a separate worker thread, which would emit a gui-update signal as each step of the task is completed. (NB: it's important to ensure that the worker thread never attempts any GUI-related operations itself).

But if the task can't be broken down into small steps, then none of the usual threading-type solutions will work. The GUI will just freeze until the task has been completed, whether threads are used or not.

For this final scenario, the only solution is to use a separate process, rather than a separate thread - i.e. make use of the multiprocessing module. Of course, this solution will only be effective if the target system has multiple CPU cores available. If there's only one CPU core to play with, there's basically nothing that can be done to help (other than switching to a different implementation of Python, or to a different language altogether).


when you do gui programming you need to use some form of multi-threading, you would have a worker thread and one that updates the gui and responds to mouse and keyboard events. There are a number of ways to do that. I recommend you have a look at this QProgressBar tutorial that has an excellent working example of how to use QProgressBar.

In the tutorial they are using QBasicTimer which is way to yield control back to the main thread so that it can respond to GUI events. By using time.sleep in your code you are blocking for one second the only thread that is running.