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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 04:13:13