事件系统
最后更新: 2026-07-21
命名空间: Snet.Model.@event, Snet.Model.data, Snet.Model.@interface
概述
Snet 事件系统提供了一个线程安全、异步优先的发布/订阅机制,用于驱动程序、核心引擎和外部消费者之间的通信。它同时支持同步(EventHandler<T>)和异步(EventHandlerAsync<T>)事件模式,并内置取消支持和异常隔离。
+------------------+ +------------------+ +-------------------+
| 驱动程序 / 数据源 | ----> | CoreUnify | ----> | 订阅者 |
| (触发事件) | | (分发事件) | | (UI, 服务层) |
+------------------+ +------------------+ +-------------------+
| | |
OnDataEventHandler IEvent.OnDataEvent IEvent.OnDataEvent
OnInfoEventHandler IEvent.OnInfoEvent IEvent.OnInfoEvent
OnLanguageEventHandler IEvent.OnLanguageEvent IEvent.OnLanguageEvent
核心类型
EventArgsAsync -- 异步事件参数基类
所有异步事件参数的基础类,继承自 System.Object。
| 成员 | 类型 | 描述 |
|---|---|---|
CancellationToken |
CancellationToken |
异步取消令牌(序列化时忽略) |
Empty |
static EventArgsAsync |
单例空实例,类似 EventArgs.Empty |
工厂方法:
CreateOrDefault(CancellationToken)-- 如果令牌不可取消则返回Empty,否则创建新实例。
// 带取消令牌构造
var args = new EventArgsAsync(cancellationToken);
// 按需创建
var args2 = EventArgsAsync.CreateOrDefault(token);
EventHandlerAsync<TEvent> -- 异步事件委托
public delegate Task EventHandlerAsync<in TEvent>(object? sender, TEvent e)
where TEvent : EventArgsAsync;
标准 EventHandler<T> 的异步版本。逆变类型参数允许传递派生类型的事件参数。
EventingWrapperAsync<TEvent>(struct 结构体 -- 值类型)
线程安全的结构体,管理 EventHandlerAsync<TEvent> 的订阅、取消订阅和触发。
| 成员 | 签名 | 描述 |
|---|---|---|
IsEmpty |
bool |
无订阅者时为 true |
| 构造函数 | (string context, Func<Exception, string, CancellationToken, Task> onException) |
创建包装器并指定异常回调 |
AddHandler |
(EventHandlerAsync<TEvent>?) |
订阅处理程序 |
RemoveHandler |
(EventHandlerAsync<TEvent>?) |
取消订阅 |
InvokeAsync |
(object? sender, TEvent) 返回 Task |
按顺序触发所有处理程序 |
Takeover |
(in EventingWrapperAsync<TEvent>) |
从另一个包装器接管所有订阅 |
关键行为:
- 委托缓存: 首次调用
InvokeAsync时,将调用列表捕获到_handlers字段中,避免重复调用GetInvocationList(),提升性能。 - 取消安全:
OperationCanceledException被捕获并静默忽略(任务被取消不算错误)。 - 异常隔离: 其他异常通过
onException回调委托处理。如果没有提供回调,异常会向外传播。 - 结构体注意事项: 由于这是值类型,
InvokeAsync在方法入口处将_handlers捕获到局部变量中,防止异步状态机快照复制到旧的空值。
事件数据类型
继承层次结构
EventArgsAsync (System.Object)
|
+-- BaseModel (Status, Message, Time)
| |
| +-- EventInfoResult -- 信息事件结果
| |
| +-- ResultModel (ResultData) -- 带结果数据
| |
| +-- EventDataResult -- 数据事件结果
|
+-- EventLanguageResult (Status, Message, Language, Time) -- 语言事件结果
EventDataResult : ResultModel -- 数据事件结果
用于 IEvent.OnDataEvent / OnDataEventAsync 数据采集事件。
| 构造函数 / 工厂方法 | 描述 |
|---|---|
EventDataResult() |
无参构造 |
EventDataResult(bool status, string message, object? resultData) |
完整构造 |
EventDataResult(EventDataResult result) |
拷贝构造 |
CreateSuccessResult(string msg) |
静态工厂 -- 成功 |
CreateSuccessResult<T>(string msg, T data) |
静态工厂 -- 成功并携带类型化数据 |
CreateFailureResult(string msg) |
静态工厂 -- 失败 |
从 ResultModel 继承:object? ResultData、GetSource<T>()、多个 GetDetails(...) 重载。
EventInfoResult : BaseModel -- 信息事件结果
用于 IEvent.OnInfoEvent / OnInfoEventAsync 状态/错误信息事件。
| 构造函数 / 工厂方法 | 描述 |
|---|---|
EventInfoResult() |
无参构造 |
EventInfoResult(bool status, string message) |
完整构造 |
EventInfoResult(EventInfoResult result) |
拷贝构造 |
CreateSuccessResult(string msg) |
静态工厂 -- 成功 |
CreateFailureResult(string msg) |
静态工厂 -- 失败 |
从 BaseModel 继承:bool Status、string? Message、DateTime Time。
EventLanguageResult : EventArgsAsync -- 语言事件结果
用于 IEvent.OnLanguageEvent / OnLanguageEventAsync 语言变更事件。不继承自 BaseModel。
| 成员 | 类型 | 描述 |
|---|---|---|
Status |
bool |
成功/失败指示 |
Message |
string? |
可读描述信息 |
Language |
LanguageType? |
目标语言(JSON 序列化为字符串) |
Time |
DateTime |
事件时间戳(默认为 DateTime.Now) |
工厂方法:
CreateSuccessResult(string msg)-- 成功CreateSuccessResult(string msg, LanguageType? language)-- 成功并指定语言CreateFailureResult(string msg)-- 失败CreateFailureResult(string msg, LanguageType? language)-- 失败并指定语言
解构方法:
GetDetails(out string? message)-- 提取消息GetDetails(out LanguageType? language)-- 提取语言类型GetDetails(out string? message, out LanguageType? language)-- 提取消息和语言GetDetails(out EventLanguageResult result)-- 提取完整结果对象
IEvent 接口
定义在 Snet.Model.@interface 中。包含 6 个事件,形成 3 对同步/异步组合:
public interface IEvent
{
// 数据事件 -- 新数据采集完成时触发
event EventHandler<EventDataResult> OnDataEvent;
event EventHandlerAsync<EventDataResult> OnDataEventAsync;
// 信息事件 -- 状态变更、错误、连接状态时触发
event EventHandler<EventInfoResult> OnInfoEvent;
event EventHandlerAsync<EventInfoResult> OnInfoEventAsync;
// 语言事件 -- UI 语言切换时触发
event EventHandler<EventLanguageResult> OnLanguageEvent;
event EventHandlerAsync<EventLanguageResult> OnLanguageEventAsync;
}
设计原则: 每种事件类型都提供了同步和异步两个版本。订阅者根据自身处理模型选择合适的版本。
内部事件触发
在内部,CoreUnify 和驱动实现通过 protected 方法 触发事件:
| 方法 | 用途 |
|---|---|
OnDataEventHandler(EventDataResult) |
触发同步数据事件 |
OnDataEventHandlerAsync(EventDataResult, CancellationToken) |
触发异步数据事件 |
OnInfoEventHandler(EventInfoResult) |
触发同步信息事件 |
OnInfoEventHandlerAsync(EventInfoResult, CancellationToken) |
触发异步信息事件 |
OnLanguageEventHandler(EventLanguageResult) |
触发同步语言事件 |
OnLanguageEventHandlerAsync(EventLanguageResult, CancellationToken) |
触发异步语言事件 |
外部消费者不应直接调用这些方法,而应通过 IEvent 接口订阅。
使用示例
订阅数据事件
using Snet.Model.data;
using Snet.Model.@event;
// 获取实现 IEvent 的驱动实例
var daq = await SomeDaqDriver.InstanceAsync(config);
// 同步订阅(在调用线程上执行)
daq.OnDataEvent += (sender, e) =>
{
if (e.Status)
{
Console.WriteLine($"[数据接收] {e.Message}");
Console.WriteLine($"载荷: {e.ResultData}");
}
else
{
Console.WriteLine($"[错误] {e.Message}");
}
};
// 异步订阅(返回 Task,适合 I/O 密集型操作)
daq.OnDataEventAsync += async (sender, e) =>
{
if (e.Status)
{
await ProcessDataAsync(e.ResultData, e.CancellationToken);
}
};
订阅信息事件
// 同步:记录连接状态
daq.OnInfoEvent += (sender, e) =>
{
Console.WriteLine($"[{e.Time:HH:mm:ss}] 状态={e.Status}, {e.Message}");
};
// 异步:写入数据库
daq.OnInfoEventAsync += async (sender, e) =>
{
await LogToDatabaseAsync(e.Status, e.Message, e.Time, e.CancellationToken);
};
订阅语言变更事件
daq.OnLanguageEvent += (sender, e) =>
{
if (e.GetDetails(out var lang))
{
Console.WriteLine($"语言已切换为: {lang}");
// 重新加载 UI 字符串资源
}
};
daq.OnLanguageEventAsync += async (sender, e) =>
{
if (e.GetDetails(out var msg, out var lang))
{
await ApplyLanguageAsync(lang, e.CancellationToken);
}
};
取消订阅(防止内存泄漏)
// 保存委托引用以便后续取消订阅
EventHandlerAsync<EventDataResult> handler = async (sender, e) =>
{
await ProcessAsync(e);
};
daq.OnDataEventAsync += handler;
// 不再需要时取消订阅
daq.OnDataEventAsync -= handler;
创建并触发事件(驱动端)
// 创建携带类型化数据的成功结果
var result = EventDataResult.CreateSuccessResult(
"温度读取完成",
new { Value = 25.6, Unit = "°C" }
);
// 触发异步事件
await OnDataEventHandlerAsync(result, CancellationToken.None);
// 创建失败结果
var errorResult = EventDataResult.CreateFailureResult("传感器超时");
// 通过 InfoResult 传递错误信息
await OnInfoEventHandlerAsync(
new EventInfoResult(false, "传感器超时"),
CancellationToken.None
);
使用 EventLanguageResult
// 宣告语言变更
var langEvent = EventLanguageResult.CreateSuccessResult(
"语言已切换为中文",
LanguageType.zh
);
// 通过 GetDetails 检查语言
if (langEvent.GetDetails(out string? msg, out LanguageType? language))
{
Console.WriteLine($"{msg} -> {language}");
}
线程安全
所有 EventingWrapperAsync<TEvent> 操作在设计上都是线程安全的:
AddHandler/RemoveHandler使用 C# 的+=/-=操作 event 字段(编译器生成的锁机制)。InvokeAsync将委托列表缓存到局部变量中,确保快照隔离。- 每个处理程序在 try/catch 块中依次调用,单个处理程序失败不会阻止其他处理程序的执行。
最佳实践
- I/O 密集型任务优先使用异步订阅(数据库写入、HTTP 调用)。
- 轻量级 UI 更新或同步日志使用同步订阅。
- 处理载荷数据前始终检查
e.Status。 - 在异步处理程序中尊重
CancellationToken以支持协作取消。 - 不再需要时取消订阅,防止长生命周期订阅者造成的内存泄漏。
- 优先使用工厂方法(
CreateSuccessResult/CreateFailureResult)而非直接使用构造函数以提高可读性。
