421 lines
16 KiB
Python
421 lines
16 KiB
Python
# -*- 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() |