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