You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

PyQt6中QThread设置parent=self后关闭窗口触发‘Destroyed while thread is still running’错误的排查求助

解决QThread重复启动与窗口关闭时的"Destroyed while thread is still running"错误

嘿,这个QThread的坑我太熟了!你遇到的问题本质是线程生命周期没管好,不管加不加parent=self,核心都是线程没被妥善停止、退出和清理导致的。我来给你拆解原因,再给你能直接用的修正代码。

问题根源拆解

  1. 不加parent=self的情况
    每次点Start都会创建新的QThread,直接覆盖旧的self.worker_thread引用。旧线程失去Python层面的引用后,垃圾回收器可能在它还没完全停稳(比如Worker的循环刚结束,但线程的事件循环还没退出)的时候就把它销毁了,这就触发了"Destroyed while thread is still running"错误。

  2. 加了parent=self的情况
    线程的父对象是窗口,窗口关闭时Qt会自动销毁所有子对象。如果此时线程还没彻底退出(哪怕Worker已经发了finished信号,线程的事件循环可能还在跑),窗口销毁线程时就会报错——毕竟线程还在运行状态就被干掉了肯定出问题。

彻底解决的代码方案

要搞定这个问题,我们需要在Worker完成/停止时主动让线程退出、等待它结束,还要断开信号连接、清理引用,同时在窗口关闭时做最后兜底。下面是修正后的完整代码:

import time
from PyQt6.QtCore import Qt, QObject, pyqtSignal, QThread
from PyQt6.QtWidgets import QApplication, QWidget, QVBoxLayout, QPlainTextEdit, QPushButton

class MyWindow(QWidget):
    def __init__(self):
        super().__init__()
        # 先把线程和Worker的引用设为None,方便后续判断状态
        self.worker_thread = None
        self.worker = None
        
        vbox = QVBoxLayout(self)
        self.progress_box = QPlainTextEdit()
        self.progress_box.setFixedHeight(100)
        self.start_btn = QPushButton("Start")
        self.stop_btn = QPushButton("Stop")
        self.start_btn.clicked.connect(self.start_worker)
        self.stop_btn.clicked.connect(self.stop_worker)
        self.stop_btn.setVisible(False)
        vbox.addWidget(self.progress_box)
        vbox.addWidget(self.start_btn, alignment=Qt.AlignmentFlag.AlignCenter)
        vbox.addWidget(self.stop_btn, alignment=Qt.AlignmentFlag.AlignCenter)

    def start_worker(self):
        # 防止重复点Start(比如线程还在跑的时候误点)
        if self.worker_thread and self.worker_thread.isRunning():
            return
            
        self.progress_box.appendPlainText("Starting")
        self.worker_thread = QThread()
        self.worker = Worker(1, 10, 0.5)
        self.worker.moveToThread(self.worker_thread)
        
        # 连接信号,这里用QueuedConnection更安全(避免跨线程直接调用的潜在问题)
        self.worker.progress.connect(self.update_progress, Qt.ConnectionType.QueuedConnection)
        self.worker.finished.connect(self.on_worker_finished)
        self.worker_thread.started.connect(self.worker.do_work)
        # 线程结束后让Qt自动销毁它,不用手动管
        self.worker_thread.finished.connect(self.worker_thread.deleteLater)
        
        self.worker_thread.start()
        self.start_btn.setVisible(False)
        self.stop_btn.setVisible(True)

    def stop_worker(self):
        if self.worker:
            self.worker.stop.emit()

    def update_progress(self, i):
        self.progress_box.appendPlainText(str(i))

    def on_worker_finished(self):
        # 让线程退出事件循环
        self.worker_thread.quit()
        # 等待线程彻底结束,别着急清理
        self.worker_thread.wait()
        # 断开所有信号连接,避免重复启动时信号叠加
        self.worker.progress.disconnect(self.update_progress)
        self.worker.finished.disconnect(self.on_worker_finished)
        self.worker_thread.started.disconnect(self.worker.do_work)
        # 清空引用,让垃圾回收器能回收这些对象
        self.worker = None
        self.worker_thread = None
        
        self.progress_box.appendPlainText("Finished")
        self.start_btn.setVisible(True)
        self.stop_btn.setVisible(False)
        
    # 窗口关闭时做最后兜底,确保线程停稳再关
    def closeEvent(self, event):
        if self.worker_thread and self.worker_thread.isRunning():
            self.stop_worker()
            self.worker_thread.quit()
            self.worker_thread.wait()
        event.accept()

class Worker(QObject):
    finished = pyqtSignal()
    progress = pyqtSignal(int)
    stop = pyqtSignal()

    def __init__(self, start_num, end_num, delay):
        super().__init__()
        self.start_num = start_num
        self.end_num = end_num
        self.delay = delay
        self.stop_requested = False
        self.stop.connect(self.request_stop)

    def do_work(self):
        i = self.start_num
        while i <= self.end_num and not self.stop_requested:
            self.progress.emit(i)
            time.sleep(self.delay)
            i += 1
        # 重置停止标记,下次启动能正常运行
        self.stop_requested = False
        self.finished.emit()

    def request_stop(self):
        self.stop_requested = True

if __name__ == "__main__":
    app = QApplication([])
    win = MyWindow()
    win.show()
    app.exec()

关键改动说明

  • 初始化引用为None:方便判断线程是否在运行,防止重复启动导致的混乱。
  • 线程退出+等待:在Worker完成后,调用quit()让线程退出事件循环,再用wait()确保它彻底停止,这是解决销毁错误的核心。
  • 断开信号连接:避免重复启动时信号被多次绑定,导致槽函数被多次触发,同时减少内存泄漏。
  • 自动销毁线程:用deleteLater()让Qt在线程结束后自动销毁它,不用手动处理对象生命周期。
  • 重写closeEvent:窗口关闭时强制检查线程状态,确保线程停稳再关闭,彻底解决关闭窗口时的报错。

这样改完之后,不管你重复启动多少次Worker,还是关闭窗口,都不会再出现那个烦人的错误了。

内容的提问来源于stack exchange,提问作者Iain

火山引擎 最新活动