PyQt5使用QThread后GUI仍冻结问题求助
问题:点击按钮后GUI冻结,耗时函数无法在后台执行
需求
点击子窗口(SubWindow)的“Start”按钮时,启动加载动画,同时让耗时函数RunSecurityCheck在后台单独运行。
实际问题
点击“Start”按钮后,RunSecurityCheck执行,但GUI出现冻结现象,加载动画无法正常显示。尝试使用QThread将耗时函数移至后台,但问题未解决。
相关代码
主窗口(Main_Window)代码
# -*- coding: utf-8 -*- from PyQt5 import QtCore, QtWidgets from Sub import SubWindow from Last import LastWindow from functools import partial import sys class Main_Window(QtWidgets.QMainWindow) : def __init__(self): super().__init__() self.resize(800, 600) self.initUI() def initUI(self): # next pushbutton self.pb_next = QtWidgets.QPushButton(self) self.pb_next.setGeometry(QtCore.QRect(540, 480, 112, 34)) self.pb_next.setText('Next') self.pb_next.clicked.connect(self.CallSub) # quit pushbutton self.pb_quit = QtWidgets.QPushButton(self) self.pb_quit.setGeometry(QtCore.QRect(660, 480, 112, 34)) self.pb_quit.setText('Quit') self.pb_quit.clicked.connect(QtCore.QCoreApplication.instance().quit) def CallSub(self): self.hide() self.SW = SubWindow() self.SW.show() print('call sub') self.SW.PB.clicked.connect(self.SW.Loading) print('loading') self.SW.PB.clicked.connect(self.RSC) print('rsc') def RSC(self): print('start rsc') self.text1 = 'host.txt' self.text2 = 'access.txt' self.thread = QtCore.QThread() self.SW.moveToThread(self.thread) self.thread.started.connect(partial(self.SW.RunSecurityCheck,self.text1,self.text2)) self.SW.finished.connect(self.thread.quit) self.SW.finished.connect(self.SW.deleteLater) self.thread.finished.connect(self.thread.deleteLater) self.SW.finished.connect(self.CallLast) self.thread.start() def CallLast(self): self.LW = LastWindow() self.SW.hide() self.LW.show() self.LW.pb_next.clicked.connect(self.CallMain) def CallMain(self): self.LW.hide() self.show() if __name__ == "__main__": app = QtWidgets.QApplication(sys.argv) mw = Main_Window() mw.show() sys.exit(app.exec_())
子窗口(SubWindow)代码
# -*- coding: utf-8 -*- from time import sleep from PyQt5 import QtCore, QtWidgets, QtGui class SubWindow(QtWidgets.QWidget) : finished = QtCore.pyqtSignal() def __init__(self): super().__init__() self.resize(800, 600) self.initUI() def initUI(self): # Quit PushButton self.pb_quit = QtWidgets.QPushButton(self) self.pb_quit.setGeometry(QtCore.QRect(660, 480, 112, 34)) self.pb_quit.setText('Quit') self.pb_quit.clicked.connect(QtCore.QCoreApplication.instance().quit) # start push button self.PB = QtWidgets.QPushButton(self) self.PB.setGeometry(QtCore.QRect(280, 240, 100, 25)) self.PB.setText('Start') # loading animation self.LB_loading = QtWidgets.QLabel(self) self.LB_loading.setGeometry(QtCore.QRect(250, 210, 200, 100)) self.loading = QtGui.QMovie("loading.gif") self.LB_loading.setMovie(self.loading) self.LB_loading.hide() def Loading(self): self.PB.hide() self.LB_loading.show() self.loading.start() def RunSecurityCheck(self,t1,t2): sleep(10) self.finished.emit()
最后窗口(LastWindow)代码
# -*- coding: utf-8 -*- from PyQt5 import QtCore, QtWidgets class LastWindow(QtWidgets.QWidget) : def __init__(self): super().__init__() self.resize(800, 600) self.initUI() def initUI(self): # Next PushButton self.pb_next = QtWidgets.QPushButton(self) self.pb_next.setGeometry(QtCore.QRect(540, 480, 112, 34)) self.pb_next.setText('Back to Main') # Quit PushButton self.pb_quit = QtWidgets.QPushButton(self) self.pb_quit.setGeometry(QtCore.QRect(660, 480, 112, 34)) self.pb_quit.setText('Quit') self.pb_quit.clicked.connect(QtCore.QCoreApplication.instance().quit) # complete label self.LB1 = QtWidgets.QLabel(self) self.LB1.setGeometry(QtCore.QRect(140, 220, 221, 18)) self.LB1.setText('Complete.')
问题根源
错误地将UI组件(SubWindow)整个移动到了子线程,但PyQt的UI组件必须在主线程中创建和操作,子线程不能直接处理UI元素,这不仅会导致GUI冻结,还可能引发程序崩溃。正确的做法是将耗时的业务逻辑(RunSecurityCheck)单独封装到Worker类,再将Worker实例移动到子线程,而不是移动UI窗口。
修复方案
1. 创建独立的Worker类
在主窗口代码中添加Worker类,把耗时的RunSecurityCheck逻辑放到这里:
class Worker(QtCore.QObject): finished = QtCore.pyqtSignal() def run(self, t1, t2): # 这里是耗时操作,示例用sleep模拟 from time import sleep sleep(10) self.finished.emit()
2. 修改Main_Window的RSC方法
不再移动SubWindow到线程,而是创建Worker实例并移动到线程:
def RSC(self): print('start rsc') self.text1 = 'host.txt' self.text2 = 'access.txt' # 创建线程和Worker实例 self.thread = QtCore.QThread() self.worker = Worker() self.worker.moveToThread(self.thread) # 连接信号与槽 self.thread.started.connect(lambda: self.worker.run(self.text1, self.text2)) self.worker.finished.connect(self.thread.quit) self.worker.finished.connect(self.worker.deleteLater) self.thread.finished.connect(self.thread.deleteLater) self.worker.finished.connect(self.CallLast) # 启动线程 self.thread.start()
3. 调整SubWindow代码
移除SubWindow中的RunSecurityCheck方法和finished信号(因为现在信号由Worker发出):
# -*- coding: utf-8 -*- from PyQt5 import QtCore, QtWidgets, QtGui class SubWindow(QtWidgets.QWidget) : def __init__(self): super().__init__() self.resize(800, 600) self.initUI() def initUI(self): # Quit PushButton self.pb_quit = QtWidgets.QPushButton(self) self.pb_quit.setGeometry(QtCore.QRect(660, 480, 112, 34)) self.pb_quit.setText('Quit') self.pb_quit.clicked.connect(QtCore.QCoreApplication.instance().quit) # start push button self.PB = QtWidgets.QPushButton(self) self.PB.setGeometry(QtCore.QRect(280, 240, 100, 25)) self.PB.setText('Start') # loading animation self.LB_loading = QtWidgets.QLabel(self) self.LB_loading.setGeometry(QtCore.QRect(250, 210, 200, 100)) self.loading = QtGui.QMovie("loading.gif") self.LB_loading.setMovie(self.loading) self.LB_loading.hide() def Loading(self): self.PB.hide() self.LB_loading.show() self.loading.start()
修复原理
- UI组件(SubWindow)始终留在主线程,确保加载动画和GUI交互正常响应。
- 耗时逻辑封装在Worker类中,移动到子线程执行,避免阻塞主线程。
- 通过信号槽机制在Worker完成后通知主线程切换窗口,保证线程间通信安全。
内容的提问来源于stack exchange,提问作者khm
相关产品推荐
相关产品推荐

