Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

PyQt GUI not responding



  • Hello dear users, i made very simple test code where i have button and this button is connected to function that is just counting to 50 000 000. And when it is done i want to set label text to Done. Everything is ok but while this function is counting my program is not responding. How can i solve this issue ?

    Also i noticed this same issue happens to me when i use time.sleep( ). While my program is "sleeping" my QUI is not responding. What i am missing guys ?

    This is just example, i need to use this in better code.

    Thank you guys.


  • Lifetime Qt Champion

    Hi,

    You are blocking the event loop.

    If you have a long lasting operation you should consider moving it to a thread.


  • Banned

    Well one of the first things you are missing is an MRE (Minimal Reproducible Example) of your code so that we more easily do the favor you are requesting which is for us to help you with our issue. MREs make it extremely easy for us to see what you are doing and test it and determine what needs to be adjusted or to explain the why it is not working the way you expect it to.

    However this one is pretty straight forward although give a exact answer will be impossible without that MRE

    So in a nutshell -- python operates with a GIL (you might want to google python GIL for a complete explanation) and Qt creates a Main Event Handler when you call QAppliction( ) -- and event handlers work this way -- the process an Event (code block) and then when that is done it returns to the Main Event Handler Queue to get the next Event so if you have a bit of code doing something it will wait to completely finish that Event or code block before doing the next one. This if not handle properly makes it appear that you GUI is not responding but it is responding -- it is queuing up all those commands and will execute them once it can of course these queued commands have to be commands that unaffected directly by your code so they have to external to your code or the command does not get queued for the same reason that queued commands do not get processed.

    There are ways to get around this, and better ways to code a project such that it does not this occurring but that takes a bit more information about how your specific code is being done so we get back to with an MRE we can help you fix the problem without it we are just guessing or generalizing and neither of those are going to help you specifically -- so I hope that helped some at least



  • @Denni-0 this is an excellent explanation, and I'm sure I'm having the same issue. I read this to mean that if we put something in our QtWidget object code like

    new_data = queue.get()
    

    or

    while(mode=="running"):
         new_data = daq.read_channels()
         time.sleep(0.1)
    

    ...then we will see a behavior like the GUI stops responding. I've tried my app both ways above with same result. So my question is, if we have another process that is doing something like sampled data from an external process at say once every 100 ms, what is the best way to do a periodic read of a process to process queue or pipe from within the QtWidget code? I will have only one process producing and one process consuming data in each channel, so I though using a queue would be simplest, but I'm not sure how to create an event when there is new data available in the queue for the QtWidget code to read or to implement a periodic event from within the QtWidget code that triggers reading from the queue. I suppose if I could implement a timer based event to run a method in my QtWidget, I could use:

    new_data = queue.get_nowait()
    

    to get the data from the queue without blocking.



  • @MatCauthon
    You have 3 possible approaches. It will depend on just how you are communicating between your Qt process and the external process which you say collects the data.

    1. If you use Qt sockets, or QProcess with the sub-process writing to stdout/stderr, you will get a Qt signal which you can put a slot onto, and that will be called when new data arrives.

    2. If new data arriving does not trigger some kind of event, you can use a QTimer to poll a (non-blocking) function (like your get_nowait()) and act on that if new data.

    3. You can use a thread to read new data (blocking is OK here), and emit a signal from the thread when it gets new data which your main UI thread has a slot on.

    #1 & #2 are simpler as they are asynchronous and can be done in your main UI thread. #3 may be required but requires more work for threading.

    If you have not already done so, you need to read through https://doc.qt.io/qt-5/signalsandslots.html as this is fundamental to how Qt works and your question.

    You could implement some of the above with Python calls instead of Qt ones if you prefer, but the principles remain the same.



  • @JonB thanks for the options. I think I like option 3. As I understand it, option 2 will tie up the processor with the continuous polling. I did try to use my own signals from the class that was sending the data, but as I understand it, every object that generates a signal needs to be a QObject, and I think that will be more work for me than option 3 actually.

    It seems to me I can have my QtWidget interface running in one process, my data updating in another process, put new data in a queue, then run:

    new_data.queue.get()
    new_data_signal_emit()
    

    in a thread from my QtWidget. It may take me some time to rearrange things, but if I get it working, I'll post the working example back here.



  • @MatCauthon
    I am fine if you wish to do it via option #3 thread. However

    As I understand it, option 2 will tie up the processor with the continuous polling.

    No, else I wouldn't have suggested it! :) In option #2 you would use a QTimer to trigger the poll calls. QTimer only runs on a repeating interval and does the poll then; the rest of the time your UI thread is doing whatever/nothing, as necessary.

    Generating a signal does require a class which derives from QObject, and you raise the signal with emit. However, that does not require that some existing class which is concerned with your data exchange be re-written to include QObject among the classes it inherits. You can always write a wrapper class, which does inherit QObject, and has your existing communicating instance as a member. This is know as encapsulation.

    Just wanted you to be aware of options, As I said, going with a thread is fine --- so long as you get your threading code right! :)


  • Banned

    Okay there are, as was pointed out, many ways to achieve this and more than were pointed out however unless you need to use Sockets you are much better off using python's multiprocessing however this is not necessary either as apparently (from my testing thus far) the QTimer seems to work outside the GIL (as do a few other things within Python and/or Qt) as such you can set up a periodic Poller (as suggested) or a periodic Sender both work on the same principle just put in different spots. However, you do have the issue that the routine regardless of when will interfere with the GUI - IF - its an extensive bit of code.

    Using QThreads requires that you are fully cognoscente of all the potential events that can occur within your code so as to know when it would be safe to fire off a call to the Main Event Handler to process queued up events and when it is not safe to do so.

    I am currently running 2 multiprocessing modules with 7 QThreads and 4 Distinct Modules in order to implement the project I am working on and each these QThreads has a Listener associated with it. Further one of these Modules currently pulls data from an external serial device and then pipes that data to the GUI for displaying. So getting this stuff to all work together without hanging up was a treat ;-) filled with interesting discovers about how these individual pieces actually work versus how they are said to work.

    Still here is a simple MUC that demonstrates using a QThread that is designed to continuously do something but without stepping on the GUI and outlines a few of the things you need to do when using a QThread -- such as how to shut it down properly. Maybe this will help you move in the direction you are wanting to go

    from PyQt5.QtCore    import QCoreApplication, QObject, QThread, pyqtSignal, pyqtSlot
    from PyQt5.QtWidgets import QApplication, QWidget, QHBoxLayout, QVBoxLayout
    from PyQt5.QtWidgets import QPushButton, QLineEdit
    
    from time import sleep as tmSleep
    
    class Processor(QObject):
        sigCount = pyqtSignal(int)
    
        def __init__(self):
            QObject.__init__(self)
            self.Connected = True
            self.StreamRdy = False
            self.Count = 0
            self.Delay = 0
            
            print('From within Thread')
    
        def ProcessRunner(self):
            print('Starting Thread Process')
            while self.Connected:
                if self.StreamRdy:
                    self.Count += 1
                    self.sigCount.emit(self.Count)
                    tmSleep(0.5)
                else:
                    self.Delay -= 1
                    self.sigCount.emit(self.Delay)
                    tmSleep(0.5)
                QCoreApplication.processEvents()
    
        @pyqtSlot()
        def CountUp(self):
            print('..... Counting Up')
            self.StreamRdy = True
    
        @pyqtSlot()
        def CountDown(self):
            print('..... Counting Down')
            self.StreamRdy = False
    
        @pyqtSlot()
        def StopCount(self):
            print('..... Stopped Counting')
            self.Connected = False
    
        @pyqtSlot()
        def RestartCount(self):
            print('..... Restarted Counting')
            if not self.Connected:
                self.Connected = True
                self.ProcessRunner()
            self.Delay = 0
            self.Count = 0
    
    
    class MainWindow(QWidget):
        sigCntUp  = pyqtSignal()
        sigCntDn  = pyqtSignal()
        sigStpCnt = pyqtSignal()
        sigRestrt = pyqtSignal()
    
        def __init__(self):
            QWidget.__init__(self)
    
            self.setWindowTitle('Main Window')    
            self.setGeometry(150, 150, 200, 200)
    
            self.btnCntUp = QPushButton('Up')
            self.btnCntUp.clicked.connect(self.CountUp)
    
            self.btnCntDn = QPushButton('Down')
            self.btnCntDn.clicked.connect(self.CountDwn)
    
            self.btnStopCnt = QPushButton('Stop')
            self.btnStopCnt.clicked.connect(self.StopCnt)
    
            self.btnRestart = QPushButton('Restart')
            self.btnRestart.clicked.connect(self.RestrtCnt)
    
            self.btnTermnat = QPushButton('Quit')
            self.btnTermnat.clicked.connect(self.TerminateThread)
            
            self.lneOutput = QLineEdit()
            
            HBox = QHBoxLayout()
            HBox.addWidget(self.btnCntUp)
            HBox.addWidget(self.btnCntDn)
            HBox.addWidget(self.btnStopCnt)
            HBox.addWidget(self.btnRestart)
            HBox.addWidget(self.btnTermnat)
            HBox.addStretch(1)
            
            VBox = QVBoxLayout()
            VBox.addWidget(self.lneOutput)
            VBox.addLayout(HBox)
            
            self.setLayout(VBox)
            
            self.EstablishThread()
    
        def EstablishThread(self):
          # Create the Object from Class
            self.Prcssr = Processor()
          # Assign the Database Signals to Slots
            self.Prcssr.sigCount.connect(self.CountRecieve)
          # Assign Signals to the Database Slots
            self.sigCntUp.connect(self.Prcssr.CountUp)
            self.sigCntDn.connect(self.Prcssr.CountDown)
            self.sigStpCnt.connect(self.Prcssr.StopCount)
            self.sigRestrt.connect(self.Prcssr.RestartCount)
    
          # Create the Thread
            self.ThredHolder = QThread()
          # Move the Listener to the Thread
            self.Prcssr.moveToThread(self.ThredHolder)
          # Assign the Listener Starting Function to the Thread Call
            self.ThredHolder.started.connect(self.Prcssr.ProcessRunner)
          # Start the Thread which launches Listener.Connect( )
            self.ThredHolder.start()
    
        @pyqtSlot(int)
        def CountRecieve(self, Count):
            self.lneOutput.setText('Count : ' + str(Count))
    
        def CountUp(self):
            print('Count Up')
            self.sigCntUp.emit()
    
        def CountDwn(self):
            print('Count Down')
            self.sigCntDn.emit()
    
        def StopCnt(self):
            print('Stop Counting')
            self.sigStpCnt.emit()
    
        def RestrtCnt(self):
            print('Restart Counting')
            self.sigRestrt.emit()
    
        def TerminateThread(self):
            print('Close Thread')
            self.ThredHolder.quit()
            if not self.ThredHolder.isRunning():
                self.ThredHolder.wait()
                print('Thread Active ',self.ThredHolder.isRunning())
            else:
                print('Thread is still Active cannot Quit')
                QCoreApplication.processEvents()
    
    if __name__ == '__main__':
        MainThred = QApplication([])
    
        MainApplication = MainWindow()
        MainApplication.show()
    
        MainThred.exec()
    

Log in to reply