base_agent/uas/event/event_bus.py

421 lines
16 KiB
Python
Raw Normal View History

2026-06-18 03:28:14 +00:00
# -*- coding: utf-8 -*-
"""
事件总线核心
基于 PyQt5 QObject/pyqtSignal + QThreadPool 实现
- 线程安全的事件提交同步 / 异步
- 按优先级排序的处理器链
- 支持同步槽 & 异步协程处理器
- 事件历史记录
- 统计信息
"""
from __future__ import annotations
import asyncio
import logging
import threading
import time
from collections import defaultdict, deque
from typing import Callable, Dict, List, Optional, Union
from PyQt5.QtCore import (
QObject, QRunnable, QThreadPool,
pyqtSignal, pyqtSlot, QTimer
)
from uas.event.event_handler import (
BaseEventHandler, HandlerDescriptor,
get_decorator_handlers
)
from uas.event.event_model import Event, EventResult, EventState
from uas.event.event_type import EventType
logger = logging.getLogger(__name__)
# ─────────────────────────────────────────────────────────────────
# PyQt5 信号载体
# ─────────────────────────────────────────────────────────────────
class _BusSignals(QObject):
"""事件总线信号集合"""
# 事件提交信号event 对象)
event_submitted = pyqtSignal(object)
# 事件处理完成信号event 对象)
event_handled = pyqtSignal(object)
# 事件处理失败信号event 对象, error_msg
event_failed = pyqtSignal(object, str)
# 事件被取消信号
event_cancelled = pyqtSignal(object)
# 无处理器信号
event_ignored = pyqtSignal(object)
# 统计更新信号total, handled, failed
stats_updated = pyqtSignal(int, int, int)
# ─────────────────────────────────────────────────────────────────
# 事件处理 Worker在 QThreadPool 工作线程中执行)
# ─────────────────────────────────────────────────────────────────
class _EventWorkerSignals(QObject):
finished = pyqtSignal(object) # Event
failed = pyqtSignal(object, str)
class _EventWorker(QRunnable):
"""
QThreadPool 工作线程中执行处理器链
每个 Event 对应一个独立 Worker
"""
def __init__(
self,
event: Event,
descriptors: List[HandlerDescriptor],
):
super().__init__()
self.event = event
self.descriptors = descriptors
self.signals = _EventWorkerSignals()
self.setAutoDelete(True)
@pyqtSlot()
def run(self):
event = self.event
event.mark_processing()
try:
for desc in self.descriptors:
# 检查事件是否已被取消
if event.is_cancelled:
logger.info(f"事件已取消,跳过后续处理器: {event}")
break
# 条件过滤
if not desc.matches(event):
continue
t0 = time.perf_counter()
try:
if desc.is_async:
# 异步处理器:在新事件循环中运行
result_data = asyncio.run(desc.fn(event))
else:
result_data = desc.fn(event)
elapsed = (time.perf_counter() - t0) * 1000
desc.call_count += 1
event.add_result(EventResult(
handler_name = desc.name,
success = True,
data = result_data,
elapsed_ms = elapsed,
))
logger.debug(
f"处理器 [{desc.name}] 完成 "
f"event={event.event_id[:8]} elapsed={elapsed:.1f}ms"
)
# 一次性处理器标记
if desc.once:
desc.fn = lambda e: None # 失效
except Exception as e:
elapsed = (time.perf_counter() - t0) * 1000
err_msg = f"{type(e).__name__}: {e}"
event.add_result(EventResult(
handler_name = desc.name,
success = False,
error = err_msg,
elapsed_ms = elapsed,
))
logger.error(
f"处理器 [{desc.name}] 异常: {err_msg}", exc_info=True
)
event.mark_done()
self.signals.finished.emit(event)
except Exception as e:
event.state = EventState.FAILED
self.signals.failed.emit(event, str(e))
logger.error(f"Worker 严重异常: {e}", exc_info=True)
# ─────────────────────────────────────────────────────────────────
# 事件总线主类
# ─────────────────────────────────────────────────────────────────
class EventBus(QObject):
"""
事件总线单例
使用方式
bus = EventBus.instance()
# 方式1注册类处理器
bus.register_handler(DeviceErrorHandler())
# 方式2注册函数处理器
bus.subscribe(EventType.DEVICE_ERROR, my_handler_fn)
# 方式3@on_event 装饰器(自动扫描)
bus.scan_decorators()
# 提交事件(异步,不阻塞主线程)
bus.post(make_event(EventType.DEVICE_ERROR, source="sensor-01"))
# 同步提交(阻塞等待所有处理器完成)
bus.post_sync(event)
"""
_instance = None
_lock = threading.Lock()
# 对外暴露的信号
event_submitted = pyqtSignal(object)
event_handled = pyqtSignal(object)
event_failed = pyqtSignal(object, str)
event_cancelled = pyqtSignal(object)
event_ignored = pyqtSignal(object)
stats_updated = pyqtSignal(int, int, int)
@classmethod
def instance(cls) -> "EventBus":
with cls._lock:
if cls._instance is None:
cls._instance = cls()
return cls._instance
def __init__(self, max_threads: int = 8, history_size: int = 500, parent=None):
super().__init__(parent)
self._pool: QThreadPool = QThreadPool.globalInstance()
self._pool.setMaxThreadCount(max_threads)
# { event_type_key: [HandlerDescriptor, ...] }(按 priority 排序)
self._handlers: Dict[str, List[HandlerDescriptor]] = defaultdict(list)
# 通配符处理器
self._wildcard_handlers: List[HandlerDescriptor] = []
# 事件历史(循环队列)
self._history: deque[Event] = deque(maxlen=history_size)
self._history_lock = threading.Lock()
# 统计
self._stats = {"total": 0, "handled": 0, "failed": 0, "ignored": 0}
self._stats_lock = threading.Lock()
logger.info(f"✅ EventBus 初始化 max_threads={max_threads}")
# ── 处理器注册 ────────────────────────────────────────────────
def register_handler(self, handler: BaseEventHandler) -> None:
"""注册类处理器(继承 BaseEventHandler"""
desc = handler.descriptor
self._add_descriptor(desc)
logger.info(f"注册类处理器: {desc}")
def subscribe(
self,
event_type: Union[EventType, str],
fn: Callable,
priority: int = 0,
condition: Optional[Callable[[Event], bool]] = None,
once: bool = False,
) -> HandlerDescriptor:
"""
注册函数处理器
:return: HandlerDescriptor可用于后续 unsubscribe
"""
desc = HandlerDescriptor(
fn = fn,
event_types = [event_type],
priority = priority,
condition = condition,
once = once,
name = fn.__name__,
)
self._add_descriptor(desc)
logger.info(f"注册函数处理器: {desc}")
return desc
def unsubscribe(self, descriptor: HandlerDescriptor) -> None:
"""注销处理器"""
for key in descriptor.event_types:
if key in self._handlers:
self._handlers[key] = [
d for d in self._handlers[key] if d is not descriptor
]
if descriptor in self._wildcard_handlers:
self._wildcard_handlers.remove(descriptor)
logger.info(f"注销处理器: {descriptor.name}")
def scan_decorators(self) -> int:
"""扫描并注册所有 @on_event 装饰的处理器"""
handlers = get_decorator_handlers()
for desc in handlers:
self._add_descriptor(desc)
logger.info(f"扫描到 {len(handlers)} 个 @on_event 处理器")
return len(handlers)
def _add_descriptor(self, desc: HandlerDescriptor):
if "*" in desc.event_types:
self._wildcard_handlers.append(desc)
self._wildcard_handlers.sort(key=lambda d: d.priority)
else:
for key in desc.event_types:
self._handlers[key].append(desc)
self._handlers[key].sort(key=lambda d: d.priority)
# ── 事件提交 ──────────────────────────────────────────────────
def post(self, event: Event) -> None:
"""
异步提交事件不阻塞调用线程
事件在 QThreadPool 工作线程中处理
"""
event.mark_pending()
self._update_stats("total")
self._add_history(event)
self.event_submitted.emit(event)
descriptors = self._get_descriptors(event)
if not descriptors:
event.state = EventState.IGNORED
self._update_stats("ignored")
self.event_ignored.emit(event)
logger.debug(f"无处理器,忽略事件: {event}")
return
worker = _EventWorker(event, descriptors)
worker.signals.finished.connect(self._on_worker_finished)
worker.signals.failed.connect(self._on_worker_failed)
self._pool.start(worker)
logger.debug(f"异步提交事件: {event} handlers={len(descriptors)}")
def post_sync(self, event: Event, timeout_ms: int = 5000) -> Event:
"""
同步提交事件阻塞等待所有处理器完成
:param timeout_ms: 超时时间毫秒
:return: 处理完成的 Event 对象
"""
event.mark_pending()
self._update_stats("total")
self._add_history(event)
descriptors = self._get_descriptors(event)
if not descriptors:
event.state = EventState.IGNORED
self._update_stats("ignored")
return event
# 同步执行处理器链
event.mark_processing()
for desc in descriptors:
if event.is_cancelled:
break
if not desc.matches(event):
continue
t0 = time.perf_counter()
try:
if desc.is_async:
result_data = asyncio.run(desc.fn(event))
else:
result_data = desc.fn(event)
elapsed = (time.perf_counter() - t0) * 1000
event.add_result(EventResult(
handler_name=desc.name, success=True,
data=result_data, elapsed_ms=elapsed
))
except Exception as e:
elapsed = (time.perf_counter() - t0) * 1000
event.add_result(EventResult(
handler_name=desc.name, success=False,
error=str(e), elapsed_ms=elapsed
))
event.mark_done()
self._update_stats("handled" if event.state == EventState.HANDLED else "failed")
return event
def post_delayed(self, event: Event, delay_ms: int) -> None:
"""延迟提交事件"""
QTimer.singleShot(delay_ms, lambda: self.post(event))
logger.debug(f"延迟 {delay_ms}ms 提交事件: {event}")
# ── 槽函数 ────────────────────────────────────────────────────
@pyqtSlot(object)
def _on_worker_finished(self, event: object):
if event.state == EventState.CANCELLED:
self._update_stats("ignored")
self.event_cancelled.emit(event)
elif event.state == EventState.FAILED:
self._update_stats("failed")
self.event_failed.emit(event, event.results[-1].error if event.results else "")
else:
self._update_stats("handled")
self.event_handled.emit(event)
s = self._stats
self.stats_updated.emit(s["total"], s["handled"], s["failed"])
logger.debug(f"事件完成: {event}")
@pyqtSlot(object, str)
def _on_worker_failed(self, event: object, error: str):
self._update_stats("failed")
self.event_failed.emit(event, error)
logger.error(f"事件处理严重失败: {event} error={error}")
# ── 内部工具 ──────────────────────────────────────────────────
def _get_descriptors(self, event: Event) -> List[HandlerDescriptor]:
"""获取匹配事件的所有处理器,按优先级合并排序"""
specific = self._handlers.get(event.type_key, [])
combined = specific + self._wildcard_handlers
combined.sort(key=lambda d: d.priority)
return combined
def _add_history(self, event: Event):
with self._history_lock:
self._history.append(event)
def _update_stats(self, key: str):
with self._stats_lock:
self._stats[key] = self._stats.get(key, 0) + 1
# ── 查询接口 ──────────────────────────────────────────────────
def get_history(
self,
event_type: Optional[Union[EventType, str]] = None,
limit: int = 50,
) -> List[Event]:
"""查询事件历史"""
with self._history_lock:
history = list(self._history)
if event_type:
key = event_type.value if isinstance(event_type, EventType) else event_type
history = [e for e in history if e.type_key == key]
return history[-limit:]
def get_stats(self) -> dict:
with self._stats_lock:
return dict(self._stats)
def clear_history(self):
with self._history_lock:
self._history.clear()
def wait_for_done(self, timeout_ms: int = 5000):
"""等待所有 Worker 完成"""
self._pool.waitForDone(timeout_ms)
# 全局总线实例
event_bus = EventBus.instance()