1

I would like to run a progress bar in a different thread from the rest of my code, but I would like to control how the progress bar updates from my main thread.

Is this something which is possible?

This is what I have so far:

import time
from PySide import QtGui
from PySide import QtCore
from PySide import QtUiTools

class progressBar(QtGui.QDialog, QtCore.QThread):

    def __init__(self, window, title=None):
        super(progressBar, self).__init__(window)
        QtCore.QThread.__init__(self)

        self.title = title or 'Progress'
        self.setupUi()
        self.show()

    def setupUi(self):
        self.setObjectName("Thinking")
        self.gridLayout = QtGui.QGridLayout(self)
        self.gridLayout.setObjectName("gridLayout")
        self.progressBar = QtGui.QProgressBar(self)
        self.gridLayout.addWidget(self.progressBar, 0, 0, 1, 1)

        # ADJUSTMENTS
        self.setMaximumSize(280, 50)
        self.setMinimumSize(280, 50)
        self.setWindowTitle(self.title)


    def increase(self, inc):
        self.progressBar.setProperty("value", inc)
        time.sleep(0.01)

    def run(self):
        for i in range(1,101):
            self.increase(i)



progressThread = progressBar(QtGui.QApplication.activeWindow())
progressThread.start()

This seems to be running the progress bar correctly inside of the thread, but it is controlled completely by the run function.

I tried removing the run function and adding this code to my main thread:

progressThread = progressBar(QtGui.QApplication.activeWindow())
progressThread.start()

for i in range(1,101):
    progressThread.increase(i)

But this didn't seem to work.

Any help with this would be great... Thanks

iGwok
  • 323
  • 5
  • 18
  • 2
    Note that `QThread`s live in the thread that created them (in this case the main thread), *not* in the thread that they manage. This code still does all its work in the main thread. You should call the progress bar's `moveToThread` method to move it to the thread managed by the `QThread` object. But also note that it's not usually a good idea to have GUI objects in threads other than the main thread. – bnaecker May 12 '16 at 01:39
  • 2
    You can't have GUI objects in anything other than the main thread/event loop. You can track progress in another thread and send signals to the main thread to update the progress bar. – Brendan Abel May 12 '16 at 06:21
  • 2
    You can also do the update via slots and signals to allow it to be updated across threads. – Steve Cohen May 12 '16 at 19:56

1 Answers1

3

I believe bnaecker, Brendan Abel and Steve Cohen already gave you the key bits of info in their comments. As they said already, you can definitely run your progress bar and your logic in separate threads, provided the UI run on the main thread.

Here is an example which should work the way you want:

import time, random
import threading

from PySide import QtCore, QtGui


class ProgressWidget(QtGui.QWidget):

    # just for the purpose of this example,
    # define a fixed number of threads to run
    nthreads = 6

    def __init__(self):
        super(ProgressWidget, self).__init__()
        self.threads = []
        self.workers = []
        self.works = [0 for i in range(self.nthreads)]
        self.setupUi()
        self.setupWorkers()
        self.runThreads()

    def drawProgessBar(self):
        self.progressBar = QtGui.QProgressBar(self)
        self.progressBar.setGeometry(QtCore.QRect(20, 20, 582, 24))
        self.progressBar.minimum = 1
        self.progressBar.maximum = 100
        self.progressBar.setValue(0)

    def setupUi(self):
        self.setWindowTitle("Threaded Progress")
        self.resize(600, 60)
        self.drawProgessBar()

    def buildWorker(self, index):
        """a generic function to build multiple workers;
        workers will run on separate threads and emit signals
        to the ProgressWidget, which lives in the main thread
        """
        thread = QtCore.QThread()
        worker = Worker(index)
        worker.updateProgress.connect(self.handleProgress)
        worker.moveToThread(thread)
        thread.started.connect(worker.work)
        worker.finished.connect(thread.quit)
        QtCore.QMetaObject.connectSlotsByName(self)
        # retain a reference in the main thread
        self.threads.append(thread)
        self.workers.append(worker)

    def setupWorkers(self):
        for i in range(self.nthreads):
            self.buildWorker(i)

    def runThreads(self):
        for thread in self.threads:
            thread.start()

    def handleProgress(self, signal):
        """you can add any logic you want here,
        it will be executed in the main thread
        """
        index, progress = signal
        self.works[index] = progress
        value = 0
        for work in self.works:
            value += work
        value /= float(self.nthreads)
        # management of special cases
        if value >= 100:
            self.progressBar.hide()
            return
        # else
        self.progressBar.setValue(value)
        print 'progress (ui) thread: %s  (value: %d)' % (threading.current_thread().name, value)


class Worker(QtCore.QObject):
    """the worker for a threaded process;
    (this is created in the main thread and
    then moved to a QThread, before starting it)
    """

    updateProgress = QtCore.Signal(tuple)
    finished = QtCore.Signal(int)

    def __init__(self, index):
        super(Worker, self).__init__()
        # store the Worker index (for thread tracking
        # and to compute the overall progress)
        self.id = index

    def work(self): 
        for i in range(100):
            print 'worker thread: %s' % (threading.current_thread().name, )
            # simulate some processing time
            time.sleep(random.random() * .2)
            # emit progress signal
            self.updateProgress.emit((self.id, i + 1))
        # emit finish signal
        self.finished.emit(1)


if __name__ == "__main__":
    import sys
    app = QtGui.QApplication(sys.argv)
    ui = ProgressWidget()
    ui.show()
    sys.exit(app.exec_())

Here's a minimal breakdown:

  • QThreads live in the main thread, where they're created (not in the thread that they manage)
  • in order to run tasks on separate threads, they must be passed to QThreads using the moveToThread method (workers need to subclass QObject for that method to be available)
  • a worker sends signals to update the progress bar and to notify they're done with their task
  • in this example we're running multiple tasks, each in its own thread. Progress signals are sent back to the main thread, where the logic to update the progress bar runs

A side note: in the console, the workers' output refers to "dummy" threads. This seems to be related to the fact that the threading module has no knowledge of QThreads (that's what I got from here, at least). Still, it seems enough to prove that workers' tasks are running on separate threads. If anyone's got more accurate info, feel free to expand.

For those want to read more on this topic, here is a link which many articles refer to.

mapofemergence
  • 458
  • 3
  • 7