简体   繁体   中英

PyQt5: QMainWindow freezes when calling a long running function

Creating a QMainWindow >> pushing start button >> that connects a long-running function with a QLabel as arg >> updating the label while running the long function.

I want to update the status of the long-running function in the GUI. But as soon as the long-running function starts the whole window freezes

import sys
import time
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (
    QApplication,
    QLabel,
    QMainWindow,
    QPushButton,
    QVBoxLayout,
    QWidget,
)

def runLongTask(label):
    label.setText('<1> sleeping for 10s ...')
    time.sleep(10)
    label.setText('<2> sleeping for 10s ...')
    time.sleep(10)
    label.setText('End')

class Window(QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setupUi()

    def setupUi(self):
        self.setWindowTitle("GUI freeze FIX")
        self.resize(350, 250)
        self.centralWidget = QWidget()
        self.setCentralWidget(self.centralWidget)
        self.label = QLabel()
        self.label.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
        self.label.setText('No Update')
        countBtn = QPushButton("Start")
        countBtn.clicked.connect(lambda: runLongTask(self.label))
        layout = QVBoxLayout()
        layout.addWidget(self.label)
        layout.addWidget(countBtn)
        self.centralWidget.setLayout(layout)

app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec())

You should use PyQt5's threads to start your long function in a different thread. That way, the main UI thread won't be busy itself, and will even be able to receive signals from the other thread and therefore update the UI.

This article is a good introduction to QThread usage.

Here is an example of a long task executed when clicking a button. The long task uses dummy time.sleep(x) to make it long, but notice how the update_ui function is passed, like a callback, to update the UI.

import sys
import time
from PyQt5.QtCore import QObject, QThread, pyqtSignal
from PyQt5.QtWidgets import (
    QApplication,
    QMainWindow,
    QProgressBar,
    QPushButton,
    QVBoxLayout,
    QWidget,
)


def long_running_function(update_ui):
    # Doing something
    time.sleep(1)
    update_ui(percent=25)

    # Doing something else
    time.sleep(1)
    update_ui(percent=50)

    # Another long thing
    time.sleep(1)
    update_ui(percent=75)

    # Almost done
    time.sleep(1)
    update_ui(percent=100)


class Worker(QObject):
    finished = pyqtSignal()
    progress = pyqtSignal(int)

    def run(self):
        # Here we pass the update_progress (uncalled!)
        # function to the long_running_function:
        long_running_function(self.update_progress)
        self.finished.emit()

    def update_progress(self, percent):
        self.progress.emit(percent)


class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        layout = QVBoxLayout()
        self.progress = QProgressBar()
        self.button = QPushButton("Start")
        layout.addWidget(self.progress)
        layout.addWidget(self.button)

        self.button.clicked.connect(self.execute)

        w = QWidget()
        w.setLayout(layout)
        self.setCentralWidget(w)
        self.show()

    def execute(self):
        self.update_progress(0)
        self.thread = QThread()
        self.worker = Worker()
        self.worker.moveToThread(self.thread)

        self.thread.started.connect(self.worker.run)
        self.worker.finished.connect(self.thread.quit)
        self.worker.finished.connect(self.worker.deleteLater)
        self.thread.finished.connect(self.thread.deleteLater)
        self.worker.progress.connect(self.update_progress)

        self.thread.start()
        self.button.setEnabled(False)

    def update_progress(self, progress):
        self.progress.setValue(progress)
        self.button.setEnabled(progress == 100)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    app.exec_()

You may manually call processEvents:

def runLongTask(label):
    label.setText('<1> sleeping for 10s ...')
    QApplication.processEvents()
    time.sleep(10)
    label.setText('<2> sleeping for 10s ...')
    QApplication.processEvents()
    time.sleep(10)
    label.setText('End')
    label.update()    

For more info, you should search for "qt Keeping the GUI Responsive", or the QThread/QtConcurrent answer here: How to make Qt work when main thread is busy?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM