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

PyQt5 QThread的requestInterrupt()未改变isInterruptRequested()值

QThread requestInterruption 不生效问题修复

核心错误点

  • 混用了两种互斥的QThread使用范式,导致中断请求发错了对象:
    代码同时做了两个冲突的操作:一是将searchWorker继承自QThread,二是又把worker实例通过moveToThread挂载到了另一个独立的QThread实例self.thread上。实际启动运行的是self.thread这个线程实例,worker只是被放到这个线程里执行的普通对象,本身并没有作为线程启动。
    停止逻辑中调用self.worker.requestInterruption(),是给从未启动过的worker这个QThread实例发中断,自然不会生效;虽然代码也给self.thread发了中断请求,但worker的run方法里调用的self.isInterruptionRequested()检查的是worker自身的中断标记,不是承载它运行的self.thread的标记,所以永远返回False。
  • 代码存在额外的逻辑错误:
    • searchWorker的构造函数super调用写错,原写法super(QThread, self).__init__()没有正确调用父类初始化方法
    • run方法里引用了不存在的self.browser、self.thread属性,就算中断触发也会直接抛出属性错误崩溃
    • 不需要在worker任务里手动调用线程的stop、wait方法,线程生命周期由信号槽自动管理即可,手动调用容易造成死锁

修复后可运行代码

直接把worker改成继承QObject(符合moveToThread的标准用法),中断检查时读取当前运行线程的中断标记即可:

import sys
from time import sleep
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(343, 362)
        self.centralwidget = QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.gridLayoutWidget = QWidget(self.centralwidget)
        self.gridLayoutWidget.setGeometry(QRect(20, 20, 301, 281))
        self.gridLayoutWidget.setObjectName("gridLayoutWidget")
        self.gridLayout = QGridLayout(self.gridLayoutWidget)
        self.gridLayout.setContentsMargins(0, 0, 0, 0)
        self.gridLayout.setObjectName("gridLayout")
        self.stopSearch = QPushButton(self.gridLayoutWidget)
        self.stopSearch.setEnabled(False)
        self.stopSearch.setObjectName("stopSearch")
        self.gridLayout.addWidget(self.stopSearch, 1, 1, 1, 1)
        self.startSearch = QPushButton(self.gridLayoutWidget)
        self.startSearch.setObjectName("startSearch")
        self.gridLayout.addWidget(self.startSearch, 1, 0, 1, 1)
        self.textEdit = QTextEdit(self.gridLayoutWidget)
        self.textEdit.viewport().setProperty("cursor", QCursor(Qt.ForbiddenCursor))
        self.textEdit.setAutoFormatting(QTextEdit.AutoBulletList)
        self.textEdit.setUndoRedoEnabled(False)
        self.textEdit.setReadOnly(True)
        self.textEdit.setAcceptRichText(False)
        self.textEdit.setObjectName("textEdit")
        self.gridLayout.addWidget(self.textEdit, 0, 0, 1, 2)
        MainWindow.setCentralWidget(self.centralwidget)
        self.retranslateUi(MainWindow)
        QMetaObject.connectSlotsByName(MainWindow)
        
    def retranslateUi(self, MainWindow):
        _translate = QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "Test Window"))
        self.stopSearch.setText(_translate("MainWindow", "Stop Task"))
        self.startSearch.setText(_translate("MainWindow", "Start Task"))

class contactPuller(QMainWindow, Ui_MainWindow):
    def __init__(self):
        super(contactPuller, self).__init__()
        self.setupUi(self)
        self.thread = QThread()
        self.worker = searchWorker()
        self.worker.moveToThread(self.thread)
        self._connectActions()
        
    def _connectActions(self):
        self.startSearch.clicked.connect(self.searchStart)
        self.stopSearch.clicked.connect(self.searchStop)
        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.reportProgress)
        self.thread.finished.connect(lambda: self.startSearch.setEnabled(True))
        self.thread.finished.connect(lambda: self.stopSearch.setEnabled(False))
        
    def searchStart(self):
        self.textEdit.clear()
        self.startSearch.setEnabled(False)
        self.stopSearch.setEnabled(True)
        # 每次启动新建线程,避免启动已退出线程报错
        self.thread = QThread()
        self.worker = searchWorker()
        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.reportProgress)
        self.thread.finished.connect(lambda: self.startSearch.setEnabled(True))
        self.thread.finished.connect(lambda: self.stopSearch.setEnabled(False))
        self.thread.start()
        
    def searchStop(self):
        self.textEdit.append('searchStop called')
        # 仅给实际运行的线程实例发中断请求
        self.thread.requestInterruption()
        
    def reportProgress(self, text):
        self.textEdit.append(text)

# 改为继承QObject,符合moveToThread标准范式
class searchWorker(QObject):
    finished = pyqtSignal()
    progress = pyqtSignal(str)
    
    def __init__(self):
        super().__init__()
        
    def save(self):
        self.progress.emit("Save Call")
    
    def run(self):
        while True:
            sleep(2)
            # 检查当前运行线程的中断标记
            is_interrupted = QThread.currentThread().isInterruptionRequested()
            self.progress.emit(f'Interrupt requested: {is_interrupted}')
            print(f'Interrupt requested: {is_interrupted}')
            if is_interrupted:
                self.save()
                self.progress.emit('Stop Call')
                # 任务完成发信号触发线程退出
                self.finished.emit()
                return

if __name__ == "__main__":
    app = QApplication([])
    app.setStyle('Fusion')
    cntctChkr = contactPuller()
    cntctChkr.show()
    sys.exit(app.exec())

补充说明

如果坚持要用继承QThread重写run的范式,就不要调用moveToThread,直接实例化QThread子类后调用start()启动,停止时直接调用子类实例的requestInterruption()即可,不需要额外创建独立的QThread实例。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 10:09:14