You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 00:25:28