Search code examples
python-3.xmultithreadingpyqt4signals-slotsqlistwidget

PyQt, QThread: GUI freeze with large amount of SIGNAL from another thread (QListWidget massive update)


I have an app that starts the GUI and performs "the heavy part" of the code in another thread using QThread. In this thread I emit a SIGNAL that is connected to the GUI Class and that performs addItem on QListWidget.

There are a massive "signaling" from this thread to the GUI and it "freeze".

Is there a way to avoid this? Have I to use another mini GUI in different thread only for QListWidget?

Thanks

EDIT: This is the thread that execute the heavy logic

class YourThreadName(QThread):
    def __init__(self, some variables):
        QThread.__init__(self)

    def __del__(self):
        self.wait()

    def run(self):
        # Here there is a for cycle that emits a SIGNAL
        for ... :
            ...
            self.emit(SIGNAL("needed_variable"), needed_variable)
            ...

In the GUI Class there are some methods, particularly:

class GUI(QtGui.QMainWindow, GUI.Ui_MainWindow):
    def __init__(self, parent=None):
        super(GUI, self).__init__(parent)
        self.setupUi(self)

    def ... (self):
        ...

    def start_main_code(self):
        self.new_thread = YourThreadName(some variables)
        self.connect(self.new_thread, SIGNAL("finished()"), self.done)
        self.connect(self.new_thread, SIGNAL("needed_variable"), self.show_variable)
        self.new_thread.start()

    def show_variable(self, data):
        self.QListWidget_object.addItem(data)

    def ... (self):
        ...

Solution

  • The script below is a Minimal, Complete, and Verifiable Example based on the information currently given in your question and comments. It emits data from a worker thread every 10ms and updates a list-widget in the GUI. On my Linux system (using Python-3.6.3, Qt-4.8.7 and PyQt-4.12.1) it does not block or freeze the GUI. There is obviously some flickering whilst the list-widget is being updated, but I am able to select items, scroll up and down, click the button, etc. And if I increase the sleep to 25ms, I don't even get any flickering.

    UPDATE:

    The performance can be improved by using setUniformItemSizes and sending the messages in batches. On my system, after a slight initial delay, the list populates with fifty thousand items almost instantly.

    import sys
    from PyQt4 import QtCore, QtGui
    
    class Worker(QtCore.QThread):
        message = QtCore.pyqtSignal(object)
    
        def run(self):
            batch = []
            for index in range(50000):
                if len(batch) < 200:
                    batch.append(index)
                    continue
                self.message.emit(batch)
                batch = []
    
    class Window(QtGui.QWidget):
        def __init__(self):
            super(Window, self).__init__()
            self.listWidget = QtGui.QListWidget()
            self.listWidget.setUniformItemSizes(True)
            self.button = QtGui.QPushButton('Start')
            self.button.clicked.connect(self.handleButton)
            layout = QtGui.QVBoxLayout(self)
            layout.addWidget(self.listWidget)
            layout.addWidget(self.button)
            self.worker = Worker()
            self.worker.message.connect(self.handleMessages)
    
        def handleMessages(self, batch):
            for message in batch:
                self.listWidget.addItem('Item (%s)' % message)
    
        def handleButton(self):
            if not self.worker.isRunning():
                self.worker.start()
    
    if __name__ == '__main__':
    
        app = QtGui.QApplication(sys.argv)
        window = Window()
        window.setGeometry(600, 50, 200, 400)
        window.show()
        sys.exit(app.exec_())