WPF+工业自动化+上位机-实现事件聚合器的跨模块交互、解耦

事件聚合器的跨模块交互、解耦

前言

在现代工业自动化系统中,上位机软件作为关键的控制与监测平台,其复杂性与模块化需求日益增长。使用WPF(Windows Presentation Foundation)构建上位机应用程序,结合事件聚合器(Event Aggregator)模式,可以有效实现跨模块的交互与解耦,提升系统的可维护性和扩展性。本文将详细介绍事件聚合器在WPF工业自动化上位机中的应用,结合具体实现代码,探讨其在模块交互与解耦中的优势

事件聚合器简介

事件聚合器是一种设计模式,用于简化组件或模块之间的通信。它充当发布者和订阅者之间的中介,允许不同的组件彼此通信而无需直接引用。这在大型应用程序中尤为有用,可以降低模块之间的耦合度,提高代码的可维护性和可扩展性。在WPF的MVVM架构中结合事件聚合器,进一步解耦View与ViewModel之间的通信

IEventAggregatorService接口设计

首先,我们定义一个IEventAggregatorService接口,提供事件订阅、取消订阅和发布的方法

namespace DataMaster.Modules.MAS.EventAggregator {
    /// <summary>
    /// 事件聚合器服务接口
    /// </summary>
    public interface IEventAggregatorService {
        /// <summary>
        /// 订阅事件
        /// </summary>
        /// <typeparam name="TEvent">要订阅的事件的类型</typeparam>
        /// <param name="action">当事件发生时要执行的操作。这是一个接受事件类型作为参数的委托(方法)</param>
        void Subscribe<TEvent>(Action<TEvent> action);

        /// <summary>
        /// 异步订阅事件
        /// </summary>
        /// <typeparam name="TEvent">要订阅的事件的类型</typeparam>
        /// <param name="action">当事件发生时要异步执行的操作。这是一个接受事件类型作为参数的异步委托(方法)</param>
        void SubscribeAsync<TEvent>(Func<TEvent, Task> action);

        /// <summary>
        /// 取消事件订阅
        /// </summary>
        /// <typeparam name="TEvent">要取消订阅的事件的类型</typeparam>
        /// <param name="action">当事件发生时应该执行的操作。这是一个方法委托,
        /// 它接受一个 TEvent 类型的参数,这个参数是之前订阅时指定的。
        /// 传入的委托应与订阅时使用的委托完全相同,包括其引用
        /// </param>
        void Unsubscribe<TEvent>(Action<TEvent> action);

        /// <summary>
        /// 取消异步事件订阅
        /// </summary>
        /// <typeparam name="TEvent">要取消订阅的事件的类型</typeparam>
        /// <param name="action">
        /// 当事件发生时应该异步执行的操作。这是一个异步方法委托,
        /// 它接受一个 <typeparamref name="TEvent"/> 类型的参数,这个参数是之前订阅时指定的
        /// 传入的委托应与订阅时使用的委托完全相同,包括其引用
        /// </param>
        void UnsubscribeAsync<TEvent>(Func<TEvent, Task> action);

        /// <summary>
        /// 发布一个事件,通知所有订阅了该事件的订阅者
        /// </summary>
        /// <typeparam name="TEvent">要发布的事件的类型</typeparam>
        /// <param name="eventToPublish">要发布的事件的实例</param>
        void Publish<TEvent>(TEvent eventToPublish);

        /// <summary>
        /// 异步发布一个事件,通知所有订阅了该事件的异步订阅者
        /// </summary>
        /// <remarks>
        /// Fire-and-Forget 模式。在单独的后台线程中启动所有订阅者的异步处理任务后立即返回,不等待这些任务完成
        /// </remarks>
        /// <typeparam name="TEvent">要发布的事件的类型</typeparam>
        /// <param name="eventToPublish">要发布的事件的实例</param>
        /// <returns>返回一个已完成的异步任务结果,表示事件发布操作已经启动</returns>
        Task PublishAsync<TEvent>(TEvent eventToPublish);
    }
}

接口方法解析

  • Subscribe:用于订阅特定类型的事件,提供一个回调函数,当事件发生时触发
  • SubscribeAsync:用于异步订阅事件,适用于需要异步处理的场景
  • Unsubscribe/UnsubscribeAsync:用于取消订阅,防止内存泄漏和无效调用
  • Publish:同步发布事件,通知所有订阅者
  • PublishAsync:异步发布事件,适用于需要异步处理的订阅者

IEventAggregatorService接口实现

接下来,我们实现IEventAggregatorService接口,创建EventAggregatorServices

using DataMaster.Modules.MAS.Loggers;
using System.Collections.Concurrent;

namespace DataMaster.Modules.MAS.EventAggregator {
    /// <summary>
    /// 事件聚合器服务,用于管理事件的订阅和发布
    /// </summary>
    internal class EventAggregatorServices : IEventAggregatorService {
        private readonly ConcurrentDictionary<Type, List<Delegate>> _subscribers = new();

        public void Subscribe<TEvent>(Action<TEvent> action) {
            List<Delegate> delegateList = _subscribers.GetOrAdd(typeof(TEvent), _ => new List<Delegate>());
            lock (delegateList) {
                if (!delegateList.Contains(action)) {
                    delegateList.Add(action);
                }
            }
        }

        public void SubscribeAsync<TEvent>(Func<TEvent, Task> action) {
            var delegateList = _subscribers.GetOrAdd(typeof(TEvent), _ => new List<Delegate>());
            lock (delegateList) {
                if (!delegateList.Contains(action)) {
                    delegateList.Add(action);
                }
            }
        }

        public void Unsubscribe<TEvent>(Action<TEvent> action) {
            if (!_subscribers.TryGetValue(typeof(TEvent), out List<Delegate>? delegateList)) {
                return;
            }
            
            lock (delegateList) {
                delegateList.Remove(action);
                if (delegateList.Count == 0) {
                    _subscribers.TryRemove(typeof(TEvent), out _);
                }
            }
        }

        public void UnsubscribeAsync<TEvent>(Func<TEvent, Task> action) {
            if (!_subscribers.TryGetValue(typeof(TEvent), out var delegateList)) {
                return;
            }
            
            lock (delegateList) {
                delegateList.Remove(action);
                if (delegateList.Count == 0) {
                    _subscribers.TryRemove(typeof(TEvent), out _);
                }
            }
        }

        public void Publish<TEvent>(TEvent eventToPublish) {
            if (!_subscribers.TryGetValue(typeof(TEvent), out List<Delegate>? delegates)) {
                return;
            }
            
            List<Action<TEvent>> actions = delegates.OfType<Action<TEvent>>().ToList();
            foreach (Action<TEvent> action in actions) {
                try {
                    action(eventToPublish);
                } catch (Exception ex) {
                    LoggerHelper.System.Error(ex, $"发布事件 {GetFullTypeName(typeof(TEvent))} 时发生错误:{ex.Message}");
                }
            }
        }

        public Task PublishAsync<TEvent>(TEvent eventToPublish) {
            if (!_subscribers.TryGetValue(typeof(TEvent), out List<Delegate>? delegates)) {
                return Task.CompletedTask;
            }
            
            List<Func<TEvent, Task>> asyncActions = delegates.OfType<Func<TEvent, Task>>().ToList();
            Task.Run(async () => {
                List<Task> tasks = new List<Task>();
                foreach (var action in asyncActions) {
                    try {
                        tasks.Add(action(eventToPublish));
                    } catch (Exception ex) {
                        LoggerHelper.System.Error(ex, $"异步处理事件 {GetFullTypeName(typeof(TEvent))} 时发生错误:{ex.Message}");
                    }
                }
                try {
                    await Task.WhenAll(tasks);
                } catch (Exception ex) {
                    LoggerHelper.System.Error(ex, $"事件 {GetFullTypeName(typeof(TEvent))} 的一个或多个异步操作失败:{ex.Message}");
                }
            });
            
            return Task.CompletedTask;
        }

        /// <summary>
        /// 获取完整的类型限定名称
        /// </summary>
        private static string GetFullTypeName(Type type) {
            if (!type.IsGenericType) {
                return type.Name;
            }
            
            string typeName = type.GetGenericTypeDefinition().Name;
            typeName = typeName[..typeName.IndexOf('`')];
            string[] typeParameters = type.GetGenericArguments().Select(GetFullTypeName).ToArray();
            return $"{typeName}<{string.Join(", ", typeParameters)}>";
        }
    }
}

实现细节解析

  • 线程安全的订阅管理:使用ConcurrentDictionarylock确保订阅列表的线程安全
  • 订阅和取消订阅:在添加或移除订阅时,检查是否已存在,防止重复添加或错误移除
  • 事件发布:在发布事件时,遍历所有订阅者并调用其回调方法,使用try-catch捕获并记录异常,防止单个订阅者的错误影响整个发布过程
  • 异步事件发布:使用Task.Run启动异步任务,不阻塞调用线程,使用Task.WhenAll等待所有异步操作完成,并捕获可能的异常

应用示例(使用依赖注入)

定义事件类型

public class DataUpdatedEvent {
    public int DataId { get; set; }
    public string NewValue { get; set; }
}

配置依赖注入容器

在应用程序启动时,配置依赖注入容器,将IEventAggregatorService注册为单例(Singleton),以确保所有模块共享同一个实例

// 注册事件聚合器服务为单例
services.AddSingleton<IEventAggregatorService, EventAggregatorServices>();

在模块中订阅事件

在需要订阅事件的模块中,通过构造函数注入获取IEventAggregatorService的实例

public class DataSubscriber {
    private readonly IEventAggregatorService _eventAggregatorService;

    // 构造函数注入事件聚合器服务
    public DataSubscriber(IEventAggregatorService eventAggregatorService) {
        _eventAggregatorService = eventAggregatorService;
        _eventAggregatorService.Subscribe<DataUpdatedEvent>(OnDataUpdated);
    }

    private void OnDataUpdated(DataUpdatedEvent evt) {
        // 处理数据更新逻辑
        Console.WriteLine($"Data with ID {evt.DataId} updated to {evt.NewValue}");
    }
}

在模块中发布事件

在需要发布事件的模块中,同样通过依赖注入获取IEventAggregatorService的实例

public class DataPublisher {
    private readonly IEventAggregatorService _eventAggregatorService;

    // 构造函数注入事件聚合器服务
    public DataPublisher(IEventAggregatorService eventAggregatorService) {
        _eventAggregatorService = eventAggregatorService;
    }

    public void UpdateData() {
        var dataEvent = new DataUpdatedEvent { DataId = 1, NewValue = "New Data" };
        // 发布事件
        _eventAggregatorService.Publish(dataEvent);
    }
}

注意事项

在使用事件聚合器模式时,取消事件订阅(Unsubscribe)是一个至关重要的步骤,尤其在以下几个方面:

一、防止内存泄漏:

当模块订阅了某个事件后,事件聚合器会持有对该模块回调方法的引用。如果模块在不需要接收该事件时没有取消订阅,事件聚合器将持续持有该引用,导致垃圾回收器无法回收模块实例,从而引发内存泄漏

二、避免无效调用:

如果模块已经卸载或不再需要处理某些事件,但仍然保留订阅,事件聚合器在发布事件时会尝试调用这些无效的回调方法,可能导致异常或不可预期的行为

三、提升应用性能:

保持不必要的订阅会增加事件发布时的处理开销,尤其是在高频事件发布的场景下。通过取消不必要的订阅,可以减少事件聚合器需要遍历和调用的回调方法数量,从而提升整体性能

为什么使用依赖注入

  • 单一实例管理:通过将IEventAggregatorService注册为单例,我们确保整个应用程序中只有一个事件聚合器实例,所有模块都共享它

  • 解耦合:模块不需要知道事件聚合器的实现细节,只需要依赖于IEventAggregatorService接口。这样,当我们需要更换事件聚合器的实现时,不需要修改模块的代码

  • 可测试性:在单元测试中,我们可以使用模拟(Mock)的IEventAggregatorService来进行测试,而不依赖于具体的实现

  • 生命周期管理:依赖注入容器负责对象的创建和销毁,我们不需要手动管理这些对象的生命周期

待扩展功能

  • 支持事件类型过滤
  • 事件的优先级处理

结语

可以在不引入额外的框架下实现自己的事件聚合器,在大型应用程序中,依赖注入是实现模块化和解耦合的重要手段。结合事件聚合器模式,我们可以有效地管理模块之间的通信,而不增加模块间的耦合度。通过使用依赖注入,我们的代码变得更加清晰、可维护,并且更容易进行单元测试。是现代软件架构设计中不可或缺的组件之一

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

涔涔OVER

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值