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

Posted

技术标签:

【中文标题】PyQt5:调用长时间运行的函数时 QMainWindow 冻结【英文标题】:PyQt5: QMainWindow freezes when calling a long running function 【发布时间】:2021-01-18 10:41:49 【问题描述】:

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

我想在 GUI 中更新长时间运行的函数的状态。但是一旦长时间运行的函数启动,整个窗口就会冻结

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

【问题讨论】:

【参考方案1】:

您应该使用 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_()

【讨论】:

谢谢。有效。一个错字,在 update_ui(progress=25) 中应该是百分比而不是进度。 @imankalyan 如果那个答案是正确的,could you accept it ? :)【参考方案2】:

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

【讨论】:

没有。 processEvents 应仅用于可能允许最少事件队列处理或实际上需要“清除”队列的特定情况。对于需要 this 很多时间(10 秒很多)的块,这不是 一个好的选择,最重要的是因为它可能会导致输入交互出现很多问题。跨度> 虽然它正在更新标签,但 GUI 仍然在休眠时冻结。

以上是关于PyQt5:调用长时间运行的函数时 QMainWindow 冻结的主要内容,如果未能解决你的问题,请参考以下文章

在 qthread 中停止长时间运行的进程

在qthread中停止长时间运行的进程

当用户关闭浏览器等待长时间运行的 Web 服务调用时会发生啥?

Spring + Hibernate+ HikariCP:如何在进行长时间运行的 REST 调用时处理数据库连接?

PyQT 在调用其他函数之前强制更新 textEdit

DOM刷新长时间运行的功能