事件系统 - Snet Docs

📢 事件系统

Snet 使用事件驱动架构,包含 3 对事件(共 6 个),定义在 IEvent 接口中,并在 CoreUnify<O, D> 中实现。


3对事件(6个事件)

每个事件都有同步和异步两个版本:

public interface IEvent
{
    // 数据事件
    event EventHandler<EventDataResult> OnDataEvent;
    event EventHandlerAsync<EventDataResult> OnDataEventAsync;

    // 信息事件
    event EventHandler<EventInfoResult> OnInfoEvent;
    event EventHandlerAsync<EventInfoResult> OnInfoEventAsync;

    // 语言事件
    event EventHandler<EventLanguageResult> OnLanguageEvent;
    event EventHandlerAsync<EventLanguageResult> OnLanguageEventAsync;
}

事件类型

OnDataEvent / OnDataEventAsync

当从设备读取数据时触发(通过 ReadAsync 或订阅轮询)。

modbus.OnDataEventAsync += async (sender, e) =>
{
    var address = e.GetSource<Address>();
    if (address != null)
    {
        foreach (var detail in address.AddressArray)
        {
            Console.WriteLine($"[{detail.AddressName}] = {detail.AddressValue?.Value} " +
                              $"(Quality: {detail.Quality})");
        }
    }
    await Task.CompletedTask;
};

EventDataResult 封装 Address 对象及其所有已填充的 AddressArray 条目。

OnInfoEvent / OnInfoEventAsync

用于信息性消息:连接状态变化、错误、警告、重连尝试。

modbus.OnInfoEventAsync += async (sender, e) =>
{
    Console.WriteLine($"[INFO] {e.Message}");
    await Task.CompletedTask;
};

EventInfoResult 包含 Message(字符串)和 Status(布尔值)。

OnLanguageEvent / OnLanguageEventAsync

当通过 SetLanguageAsync() 更改语言时触发。传播到所有 CoreUnify 实例。

modbus.OnLanguageEventAsync += async (sender, e) =>
{
    Console.WriteLine($"Language changed to: {e.LanguageType}");
    // 刷新 UI 标签
    await Task.CompletedTask;
};

EventLanguageResult 包含 LanguageType(枚举:zhen)。


EventingWrapperAsync<T>

异步事件在内部使用 EventingWrapperAsync<T>,这是一个提供线程安全异步事件调用的结构体:

// 内部实现(简化版)
private EventingWrapperAsync<EventDataResult> OnDataEventWrapperAsync;

// 触发事件(在 CoreUnify 内部)
protected void OnDataEventHandler(object? sender, EventDataResult e)
{
    OnDataEvent?.Invoke(sender, e);                      // 同步订阅者
    _ = OnDataEventWrapperAsync.InvokeAsync(sender, e);   // 异步订阅者
}

同步事件先触发,然后异步包装器调用所有异步订阅者。这确保同步和异步处理程序都能收到事件,无论它们订阅了哪个事件。


订阅和取消订阅

标准的 C# 事件模式:

// 订阅
modbus.OnDataEventAsync += MyHandler;
modbus.OnInfoEvent += MyInfoHandler;

// 取消订阅
modbus.OnDataEventAsync -= MyHandler;
modbus.OnInfoEvent -= MyInfoHandler;

始终在释放之前取消订阅以防止内存泄漏:

modbus.OnDataEventAsync -= MyHandler;
await modbus.DisposeAsync();

事件流程图

设备读取完成
        |
        v
字节解析为 AddressArray(包含 AddressValue + Quality)
        |
        v
创建 EventDataResult
        |
        +---> OnDataEvent?.Invoke(sender, result)         [同步]
        +---> OnDataEventWrapperAsync.InvokeAsync(...)     [异步]
                   |
                   +---> Handler1(sender, result)
                   +---> Handler2(sender, result)
                   +---> HandlerN(sender, result)

语言事件传播

与每个实例独立的数据/信息事件不同,语言事件通过静态 LanguageHandler 传播:

// 在一个实例中设置语言会触发所有实例中的 OnLanguageEvent
await modbus.SetLanguageAsync(LanguageType.en);

// 每个已注册的 CoreUnify 实例都会触发:
// OnLanguageEvent?.Invoke(sender, languageResult)
// OnLanguageEventWrapperAsync.InvokeAsync(sender, languageResult)

这确保所有已连接的设备、UI 组件和日志记录器同时切换语言。


最佳实践

  1. 优先使用异步处理程序 -- 使用 OnDataEventAsync 以避免阻塞 I/O 线程
  2. 保持处理程序快速 -- 不要在事件处理程序中执行繁重工作;将工作卸载到 Channel
  3. 释放时取消订阅 -- 在调用 DisposeAsync() 之前移除处理程序
  4. 检查 sender -- 在多设备场景中,检查 sender 以识别哪个设备触发了事件
  5. 使用 e.GetSource<T>() -- 通过 GetSource<T>() 扩展方法从 EventDataResult 中提取类型化数据