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
相关产品推荐
相关产品推荐

