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 冻结的主要内容,如果未能解决你的问题,请参考以下文章
当用户关闭浏览器等待长时间运行的 Web 服务调用时会发生啥?