Search code examples
pythonpyqtpysideqthreadnuke

Running progress bar in a different thread - Pyside


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


Solution

  • 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.