PyQt6中QThread设置parent=self后关闭窗口触发‘Destroyed while thread is still running’错误的排查求助
解决QThread重复启动与窗口关闭时的"Destroyed while thread is still running"错误
嘿,这个QThread的坑我太熟了!你遇到的问题本质是线程生命周期没管好,不管加不加parent=self,核心都是线程没被妥善停止、退出和清理导致的。我来给你拆解原因,再给你能直接用的修正代码。
问题根源拆解
不加
parent=self的情况:
每次点Start都会创建新的QThread,直接覆盖旧的self.worker_thread引用。旧线程失去Python层面的引用后,垃圾回收器可能在它还没完全停稳(比如Worker的循环刚结束,但线程的事件循环还没退出)的时候就把它销毁了,这就触发了"Destroyed while thread is still running"错误。加了
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




