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

Python:如何在上下文管理器中捕获守护线程心跳停止的异常?

解决上下文管理器中捕获守护线程异常的问题

要让心跳线程的异常能终止上下文管理器,核心是将线程内的异常传递到主线程——默认情况下,守护线程的异常不会主动传播到主线程,需要手动实现传递逻辑。以下是具体方案:

关键思路

  1. 用共享可变对象(比如列表)存储心跳线程的异常(列表是线程安全的,适合跨线程传递数据)。
  2. 心跳线程捕获所有异常后,存入共享对象,并主动中断主线程(打破主线程的阻塞状态,让上下文管理器及时处理异常)。
  3. 上下文管理器捕获主线程的中断信号,将其转换为实际的心跳异常抛出。
  4. 在上下文管理器的收尾阶段再次检查异常,确保不会遗漏。

完整代码实现

from contextlib import contextmanager
from threading import Thread, Event
from time import sleep
import threading


@contextmanager
def plc():
    stop_event = Event()
    # 用列表存储异常(可变对象,线程间可共享)
    error_store = []

    try:
        # 启动心跳线程,传入停止事件和异常存储对象
        hb_t = Thread(target=heartbeat_task,
                      args=(stop_event, error_store),
                      daemon=True)
        hb_t.start()

        try:
            # 将控制权交给with块内的业务代码
            yield
        except KeyboardInterrupt:
            # 如果是心跳线程触发的中断,抛出实际业务异常
            if error_store:
                raise error_store[0] from None
            # 否则重新抛出原中断信号
            raise
    except Exception:
        # 重新抛出所有异常,终止上下文管理器
        raise
    finally:
        # 停止心跳线程并等待其结束
        stop_event.set()
        hb_t.join()
        # 收尾阶段再次检查异常,确保不会遗漏
        if error_store:
            raise error_store[0]
        print("Heartbeat stopped")


def heartbeat_task(stop_event, error_store):
    value = False
    try:
        while not stop_event.is_set():
            value = not value
            print("Heartbeat: " + str(value))
            
            # 模拟心跳发送失败的异常(可替换为实际业务逻辑)
            if value:
                raise ValueError("心跳发送超时,连接中断")
            
            sleep(1)
    except Exception as e:
        # 保存异常到共享对象
        error_store.append(e)
        # 触发停止事件
        stop_event.set()
        # 中断主线程的阻塞操作(比如sleep),让上下文管理器及时处理
        threading.interrupt_main()


def main():
    try:
        with plc():
            while True:
                print("Program running")
                sleep(5)
    except Exception as e:
        print(f"上下文管理器终止,原因:{str(e)}")

if __name__ == '__main__':
    main()

代码说明

  • 异常存储:error_store列表用于跨线程传递异常对象,因为列表是可变类型,线程间修改无需额外锁(这里仅存储单个异常,逻辑简单)。
  • 线程中断:threading.interrupt_main()向主线程发送KeyboardInterrupt信号,打破主线程的sleep或其他阻塞操作,确保上下文管理器能及时响应异常。
  • 异常转换:上下文管理器捕获KeyboardInterrupt后,优先检查error_store,存在心跳异常时抛出实际业务异常,隐藏中断信号的底层细节。
  • 收尾检查:在finally块中再次检查异常,避免主线程未处于阻塞状态时遗漏异常抛出逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 18:45:57