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

使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 23:42:45