PyQt5:调用长 运行 函数时 QMainWindow 冻结

PyQt5: QMainWindow freezes when calling a long running function

创建一个 QMainWindow >> 按下开始按钮 >> 将长运行 函数与 QLabel 作为参数连接 >> 在 运行 长函数时更新标签。

我想在 GUI 中更新 long-运行 函数的状态。但是一旦 long-运行 函数启动,整个 window 就会冻结

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())

您可以手动调用 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()    

有关更多信息,您应该搜索“qt Keeping the GUI Responsive”, 或此处的 QThread/QtConcurrent 答案:How to make Qt work when main thread is busy?

您应该使用 PyQt5 的线程在不同的线程中启动您的长函数。这样,主 UI 线程本身就不会忙,甚至可以从其他线程接收信号并因此更新 UI.

This article很好的介绍了QThread的用法

这是单击按钮时执行的长任务的示例。长任务使用虚拟 time.sleep(x) 使其变长,但请注意 update_ui 函数是如何传递的,就像回调一样,以更新 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_()