PyQtSignal无法正常触发求助:点击停止按钮程序未终止
问题原因
核心问题是子线程被阻塞,无法处理信号事件:
Runner.start()方法在子线程启动后,进入了包含while循环和time.sleep的阻塞逻辑,完全占用子线程,导致它没有机会处理PyQt的事件队列(包括stopSignal触发的槽函数调用)。- 直接调用
runner.stop()是同步调用方法,不需要经过事件循环,能立即修改run_flag终止循环;但通过stopSignal.emit()发送信号时,信号需要子线程的事件循环处理,而子线程正被阻塞,根本无法响应。
修复方案
把阻塞式循环替换为QTimer,让子线程的事件循环正常运行,从而可以接收并处理信号。修改后的代码如下:
import sys import logging from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QPushButton) from PyQt5.QtCore import QObject, pyqtSignal, QThread, QTimer logging.basicConfig(level=logging.INFO) class Runner(QObject): stopped = pyqtSignal() def __init__(self, name): super().__init__() self.name = name self.run_flag = True # 创建定时器触发任务 self.timer = QTimer() self.timer.setInterval(12000) # 对应原逻辑10秒任务+2秒间隔的总时长 self.timer.timeout.connect(self.timeoutSlot) def start(self): logging.info(f"start {self.name}") self.timer.start() def stop(self): logging.warning(f"stop called for {self.name}.") self.run_flag = False self.timer.stop() self.stopped.emit() def timeoutSlot(self): # 模拟长时间任务 logging.info(f"{self.name} is running") class MainWindow(QMainWindow): stopSignal = pyqtSignal() def __init__(self): super().__init__() self._central_widget = QWidget() self._central_layout = QVBoxLayout() self.start_btn = QPushButton("start") self.stop_btn = QPushButton("stop") self._central_layout.addWidget(self.start_btn) self._central_layout.addWidget(self.stop_btn) self._central_widget.setLayout(self._central_layout) self.setCentralWidget(self._central_widget) self.start_btn.clicked.connect(self.start) self.stop_btn.clicked.connect(self.stop) self.connects = [] self.runners = [] def start(self): for i in range(2): thread = QThread() runner = Runner(f"runner_{i}") runner.moveToThread(thread) thread.started.connect(runner.start) conn = self.stopSignal.connect(runner.stop) self.connects.append(conn) runner.stopped.connect(thread.quit) runner.stopped.connect(runner.deleteLater) self.runners.append(runner) thread.finished.connect(thread.deleteLater) thread.start() # 保存线程和runner引用,避免被GC提前回收 setattr(self, f"thread_{i}", thread) setattr(self, f"runner_{i}", runner) def stop(self): print("stop btn clicked!") self.stopSignal.emit() # 清理连接,避免后续重复连接导致异常 for conn in self.connects: self.stopSignal.disconnect(conn) self.connects.clear() self.runners.clear() if __name__ == '__main__': app = QApplication(sys.argv) win = MainWindow() win.show() sys.exit(app.exec())
关键修改点
- 用
QTimer替代while循环和time.sleep:定时器在事件循环中触发任务,不会阻塞线程,让线程能随时响应信号。 - 在
stop方法中先停止定时器,确保任务不会继续触发。 - 保留原逻辑的执行频率,将定时器间隔设为12秒(对应原代码10秒任务+2秒间隔的总时长)。
额外说明
如果实际任务无法用QTimer(比如任务执行时间不固定),可以改用QThread的requestInterruption(),并在任务中定期检查thread().isInterruptionRequested(),同时要确保任务没有长时间的不可中断阻塞操作(若有,需拆分成可中断的步骤)。
内容的提问来源于stack exchange,提问作者Poison
相关产品推荐
相关产品推荐

