Files
WQ_GUI/src/gui/core/event_bus.py
2026-07-01 13:19:46 +08:00

93 lines
3.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
轻量级事件总线
支持 subscribe(event_name, callback)、unsubscribe(event_name, callback)
和 publish(event_name, data),用于步骤面板间的去中心化参数传导。
2026-06-30 修复:
- publish() 中的 except Exception: pass 改为 traceback 日志输出(静默吞异常会导致 bug 无法发现)
- 新增 unsubscribe() 方法,允许面板销毁时清理订阅,防止内存泄漏和野指针回调
"""
import sys
import threading
import traceback
from collections import defaultdict
from typing import Any, Callable, Dict, List, Optional
class EventBus:
"""发布-订阅事件总线(线程安全)。"""
def __init__(self):
self._subscribers: Dict[str, List[Callable]] = defaultdict(list)
self._lock = threading.Lock()
# 异常日志回调(可选注入,供 LogManager 使用)
self._error_logger: Optional[Callable[[str], None]] = None
def set_error_logger(self, logger: Callable[[str], None]):
"""注入异常日志回调。若不设置,异常信息输出到 stderr。"""
self._error_logger = logger
def subscribe(self, event_name: str, callback: Callable[[dict], None]):
"""订阅事件。callback 接收一个 dict 作为事件数据。"""
with self._lock:
if callback not in self._subscribers[event_name]:
self._subscribers[event_name].append(callback)
def unsubscribe(self, event_name: str, callback: Callable[[dict], None]):
"""取消订阅。面板销毁或不再需要接收事件时调用,防止野指针回调。"""
with self._lock:
subs = self._subscribers.get(event_name)
if subs and callback in subs:
subs.remove(callback)
# 清理空列表,避免字典膨胀
if not subs:
del self._subscribers[event_name]
def publish(self, event_name: str, data: Dict[str, Any]):
"""发布事件,通知所有订阅者。订阅者异常不再静默吞掉,而是输出 traceback。
迭代订阅者列表的副本,防止回调中调用 unsubscribe() 导致跳过后续订阅者。
2026-07-01 修复:捕获 RuntimeError(sip 野指针异常)防止已删除的 C++ 对象回调崩溃。
"""
with self._lock:
snapshot = list(self._subscribers.get(event_name, []))
for callback in snapshot:
try:
callback(data)
except RuntimeError:
# ★ 野指针保护:C++ 对象已删除时 sip 抛出 RuntimeError,静默跳过。
cb_name = getattr(callback, '__name__', None) or repr(callback)
err_msg = (
f"[EventBus] 事件 '{event_name}' 的订阅者 {cb_name!r} C++ 对象已销毁,跳过。\n"
+ traceback.format_exc()
)
if self._error_logger:
try:
self._error_logger(err_msg)
except Exception:
print(err_msg, file=sys.stderr, flush=True)
else:
print(err_msg, file=sys.stderr, flush=True)
except Exception:
cb_name = getattr(callback, '__name__', None) or repr(callback)
err_msg = (
f"[EventBus] 事件 '{event_name}' 的订阅者 {cb_name!r} 抛出异常:\n"
+ traceback.format_exc()
)
if self._error_logger:
try:
self._error_logger(err_msg)
except Exception:
print(err_msg, file=sys.stderr, flush=True)
else:
print(err_msg, file=sys.stderr, flush=True)
# 全局单例
global_event_bus = EventBus()