PyQt Progress Bar Update with Threads

15,282

There are really two problems. One thing I have noticed is that Python threads are greedy if they are not used for IO operations like reading from a serial port. If you tell a thread to run a calculation or something that is not IO related a thread will take up all of the processing and doesn't like to let the main thread / event loop run. The second problem is that signals are slow ... very slow. I've noticed that if you emit a signal from a thread and do it very fast it can drastically slow down a program.

So at the heart of the issue, the thread is taking up all of the time and you are emitting a signal very very fast which will cause slow downs.

For clarity and ease of use I would use the new style signal and slots. http://pyqt.sourceforge.net/Docs/PyQt4/new_style_signals_slots.html

class progressThread(QThread):

    progress_update = QtCore.Signal(int) # or pyqtSignal(int)

    def __init__(self):
        QThread.__init__(self)

    def __del__(self):
        self.wait()


    def run(self):
        # your logic here
        while 1:      
            maxVal = 100
            self.progress_update.emit(maxVal) # self.emit(SIGNAL('PROGRESS'), maxVal)
            # Tell the thread to sleep for 1 second and let other things run
            time.sleep(1)

To connect to the new style signal

...
self.progress_thread.start()
self.process_thread.progress_update.connect(self.updateProgressBar) # self.connect(self.progress_thread, SIGNAL('PROGRESS'), self.updateProgressBar)
...

EDIT Sorry there is another problem. When a signal calls a function you cannot stay in that function forever. That function is not running in a separate thread it is running on the main event loop and the main event loop waits to run until you exit that function.

Update progress sleeps for 1 second and keeps looping. The hanging is coming from staying in this function.

def updateProgressBar(self, maxVal):

    for i in range(maxVal):
        self.ui.progressBar.setValue(self.ui.progressBar.value() + 1)
        time.sleep(1)
        maxVal = maxVal - 1

        if maxVal == 0:
            self.ui.progressBar.setValue(100)

It would be better to write the progress bar like

class progressThread(QThread):

    progress_update = QtCore.Signal(int) # or pyqtSignal(int)

    def __init__(self):
        QThread.__init__(self)

    def __del__(self):
        self.wait()


    def run(self):
        # your logic here
        while 1:      
            maxVal = 1 # NOTE THIS CHANGED to 1 since updateProgressBar was updating the value by 1 every time
            self.progress_update.emit(maxVal) # self.emit(SIGNAL('PROGRESS'), maxVal)
            # Tell the thread to sleep for 1 second and let other things run
            time.sleep(1)


def updateProgressBar(self, maxVal):
    self.ui.progressBar.setValue(self.ui.progressBar.value() + maxVal)
    if maxVal == 0:
        self.ui.progressBar.setValue(100)
Share:
15,282
nuriselcuk
Author by

nuriselcuk

I love to help people who has struggled about codes. Love the support and share my thoughts with any other people in world

Updated on June 04, 2022

Comments

  • nuriselcuk
    nuriselcuk almost 2 years

    I have been writing a program which runs a remote script on server. So, I need to show the progress with a bar but somehow when I run my code, GUI start to freeze. I have used QThread and SIGNAL but unfortunately couldnt be succedeed.

    Here is my code below;

    class dumpThread(QThread):
    
        def __init__(self):
            QThread.__init__(self)
    
        def __del__(self):
            self.wait()
    
        def sendEstablismentCommands(self, connection):
    
            # Commands are sending sequently with proper delay-timers #
    
            connection.sendShell("telnet localhost 21000")
            time.sleep(0.5)
            connection.sendShell("admin")
            time.sleep(0.5)
            connection.sendShell("admin")
            time.sleep(0.5)
            connection.sendShell("cd imdb")
            time.sleep(0.5)
            connection.sendShell("dump subscriber")
    
            command = input('$ ')
    
        def run(self):
            # your logic here              
            # self.emit(QtCore.SIGNAL('THREAD_VALUE'), maxVal)
            self.sendEstablismentCommands(connection)    
    
    class progressThread(QThread):
    
        def __init__(self):
            QThread.__init__(self)
    
        def __del__(self):
            self.wait()
    
    
        def run(self):
            # your logic here
            while 1:      
                maxVal = 100
                self.emit(SIGNAL('PROGRESS'), maxVal)
    
    class Main(QtGui.QMainWindow):
        def __init__(self):
            QtGui.QMainWindow.__init__(self)
            self.ui = Ui_MainWindow()
            self.ui.setupUi(self)
            self.ui.connectButton.clicked.connect(self.connectToSESM)
    
    
    
        def connectToSESM(self):
            ## Function called when pressing connect button, input are being taken from edit boxes. ##
            ## dumpThread() method has been designed for working thread seperate from GUI. ##
    
            # Connection data are taken from "Edit Boxes"
            # username has been set as hardcoded
    
            ### Values Should Be Defined As Global ###
            username = "ntappadm"
            password = self.ui.passwordEdit.text()
            ipAddress = self.ui.ipEdit.text()
    
            # Connection has been established through paramiko shell library
            global connection
    
            connection = pr.ssh(ipAddress, username, password)
            connection.openShell()
            pyqtRemoveInputHook()  # For remove unnecessary items from console
    
            global get_thread
    
            get_thread = dumpThread() # Run thread - Dump Subscriber
            self.progress_thread = progressThread()
    
            self.progress_thread.start()
            self.connect(self.progress_thread, SIGNAL('PROGRESS'), self.updateProgressBar)
    
            get_thread.start()     
    
    
    
    
        def updateProgressBar(self, maxVal):
    
            for i in range(maxVal):
                self.ui.progressBar.setValue(self.ui.progressBar.value() + 1)
                time.sleep(1)
                maxVal = maxVal - 1
    
                if maxVal == 0:
                    self.ui.progressBar.setValue(100)
    
        def parseSubscriberList(self):
            parsing = reParser()
    
        def done(self):
            QtGui.QMessageBox.information(self, "Done!", "Done fetching posts!")
    
    
    
    
    if __name__ == "__main__":
        app = QtGui.QApplication(sys.argv)
        main = Main()
        main.show()
        sys.exit(app.exec_())
    

    I am expecting to see updateProgressBar method has called with SIGNAL, so process goes through seperate thread. I coudlnt find where I am missing.

    Thanks for any help

  • nuriselcuk
    nuriselcuk about 7 years
    Thanks for your answer, but my Gui is still freezing. Looks like no change. By the way I used pyqtSignal(int)
  • justengel
    justengel about 7 years
    I edited my answer and added information about staying in updateProgressBar for too long.
  • nuriselcuk
    nuriselcuk about 7 years
    Yes, you are right, code is waiting for end up the for loop in progressBarUpdate. I have updated now it's working. Thanks for all