使用QThread实现答题倒计时遇问题:线程运行时被销毁
PyQt5答题倒计时QThread错误修复及优化
错误原因
- 局部线程对象被提前销毁:
present方法中创建的QThread和Worker是局部变量,方法执行完毕后被Python垃圾回收,但此时线程可能仍在运行,Qt检测到线程销毁时未停止,抛出QThread: Destroyed while thread is still running错误。 - 按键检测逻辑不合理:使用
keyboard模块在update方法中每秒检测一次按键,无法实时响应输入,且跨线程调用UI控件存在潜在风险。 - UI操作逻辑错误:
update方法中初始化pressed = QLabel()会创建空标签,后续not pressed.isHidden()的判断无实际意义,应该初始化为None再判断是否选中有效答案标签。
修复方案
1. 将线程和Worker转为实例变量
在Question类的__init__方法中初始化线程和Worker对象,避免局部变量被提前销毁,保证线程生命周期与Question实例一致。
2. 调整线程销毁的连接顺序
确保线程先执行quit,等待线程完全结束后再销毁对象,添加thread.finished.connect(thread.deleteLater)避免提前销毁线程。
3. 替换按键检测方式
使用Qt原生的QShortcut实现快捷键绑定,在主线程处理按键事件,符合Qt事件循环机制,避免跨线程问题。
4. 修正UI操作逻辑
将update方法改为专注于倒计时更新,按键检测逻辑单独提取为方法,确保UI操作都在主线程执行。
优化后的完整代码
import sys import random from PyQt5.QtWidgets import (QApplication, QWidget, QLabel, QVBoxLayout, QHBoxLayout) from PyQt5.QtCore import QThread, QObject, pyqtSignal, pyqtSlot, Qt from PyQt5.QtGui import QKeySequence class Worker(QObject): finished = pyqtSignal() progress = pyqtSignal(int) # 传递已流逝的秒数 def run(self): for i in range(30): self.progress.emit(i + 1) QThread.sleep(1) # 使用Qt线程休眠替代time.sleep self.finished.emit() class Question(QObject): def __init__(self, question: tuple[str, list[str]], question_label, answer_labels) -> None: super().__init__() self.question_text, self.answers = question self.true_answer = self.answers[0] random.shuffle(self.answers) # 保存UI控件引用 self.question_label = question_label self.answer_labels = answer_labels # 初始化线程与Worker 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_countdown) # 绑定数字快捷键 self._setup_shortcuts() def _setup_shortcuts(self): # 绑定1-4数字键到对应选项 for idx, label in enumerate(self.answer_labels, start=1): shortcut = QShortcut(QKeySequence(str(idx)), self.question_label.parent()) shortcut.activated.connect(lambda l=label: self.on_answer_selected(l)) @pyqtSlot(int) def update_countdown(self, elapsed): # 这里可以更新界面上的倒计时显示(示例中绑定到主窗口的倒计时标签) pass def on_answer_selected(self, selected_label): # 处理答案选中逻辑 selected_text = selected_label.text().split(') ')[1] # 重置所有选项样式 for label in self.answer_labels: label.setStyleSheet("") # 标记选中的选项 selected_label.setStyleSheet("border: 1px solid black;") # 判断答案对错 if selected_text == self.true_answer: print("回答正确!") else: print(f"回答错误,正确答案:{self.true_answer}") # 停止倒计时线程 self.thread.quit() self.thread.wait() def present(self): # 渲染题目与选项 self.question_label.setText(self.question_text) for idx, label in enumerate(self.answer_labels): label.setText(f'{["א", "ב", "ג", "ד"][idx]} {self.answers[idx]}') # 启动倒计时线程 self.thread.start() # 主窗口示例 class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle("益智答题游戏") self.resize(400, 300) layout = QVBoxLayout() # 题目标签 self.question_label = QLabel() self.question_label.setAlignment(Qt.AlignCenter) self.question_label.setStyleSheet("font-size: 16px; padding: 10px;") layout.addWidget(self.question_label) # 选项布局 answer_layout = QVBoxLayout() self.answer_labels = [] for _ in range(4): label = QLabel() label.setAlignment(Qt.AlignCenter) label.setStyleSheet("font-size: 14px; padding: 8px; margin: 4px; background-color: #f0f0f0;") answer_layout.addWidget(label) self.answer_labels.append(label) layout.addLayout(answer_layout) # 倒计时标签 self.countdown_label = QLabel("剩余时间:30秒") self.countdown_label.setAlignment(Qt.AlignRight) self.countdown_label.setStyleSheet("font-size: 12px; color: #666;") layout.addWidget(self.countdown_label) self.setLayout(layout) # 初始化题目 sample_question = ("1+1等于几?", ["2", "3", "4", "5"]) self.question = Question(sample_question, self.question_label, self.answer_labels) # 绑定倒计时更新到主窗口标签 def update_countdown(elapsed): remaining = 30 - elapsed self.countdown_label.setText(f"剩余时间:{remaining}秒") if remaining == 0: print("时间到!") # 这里可以添加时间到的处理逻辑 self.question.update_countdown = update_countdown # 显示题目 self.question.present() if __name__ == "__main__": app = QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec_())
关键说明
- 线程安全:所有UI操作均通过主线程执行,Qt信号槽自动处理跨线程通信,避免线程冲突。
- 资源管理:线程结束时通过
quit()+wait()确保线程完全停止,再通过deleteLater()销毁对象,避免内存泄漏。 - 交互优化:使用
QShortcut实时响应按键,替代每秒轮询的低效方式,提升用户体验。
内容的提问来源于stack exchange,提问作者eitan piamenta
相关产品推荐
相关产品推荐

