# -*- 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()