C# 订阅发布方式技术总结(观察者模式)

一 技术目标

  客户端程序开发的过程中,订阅发布技术是使用频次比较多的,如:界面通知,操作通知等,订阅发布可以分为应用内的和应用间的,本次主要是总结应用内的观察者模式实现,应用间的可通过第三方工具(MQ,Redis,RPC等)来实现。

  订阅发布,关注的是效率,大量通知产生的时候,可以及时处理完成,所以从完成时间这个标准来衡量我使用到的集中方式。

二 技术过程

  • 委托

  委托、事件是.net提供的实现方式,简单实现如下:

 

       // 1. 委托
        public delegate void TestDelegate(string msg);

        // 2. 事件
        public event TestDelegate TestEvent;

        // 3. 订阅事件(示意)
        public void SubjectEvent()
        {
            TestEvent += (msg) =>
            {
                Console.WriteLine($"事件接受消息:{msg}");
            };
        }

        // 4. 发布
        public void PublishEvent(string msg)
        {
            TestEvent?.Invoke(msg);
        }

 

  

  • 事件总线

  基于事件总线的通知,可以以类的方式,进行数据传递,使用字典记录当前Handle程序和发布程序即可,具体程序如下:

      

using Error.Extension;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Util.EventBus.Obj
{
    public class EventBus
    {
        #region 单例

        private static EventBus eventBus = new EventBus();

        public static EventBus Instance // 单例
        {
            get { return eventBus; }
        }

        #region 构造函数

        private EventBus()
        {
        }

        #endregion 构造函数

        #endregion 单例

        #region 变量

        private readonly object locObj = new object(); // 订阅/发布线程锁

        private static Dictionary<Type, List<object>> dicEvents = new Dictionary<Type, List<object>>(); // 事件数据的存储,目前采用内存字典

        #endregion 变量

        #region 方法

        #region 判断事件处理程序是否相同

        private readonly Func<object, object, bool> eventHandlerEquals = (o1, o2) =>
        {
            var o1Type = o1.GetType();
            var o2Type = o2.GetType();
            if (o1Type.IsGenericType &&
                o1Type.GetGenericTypeDefinition() == typeof(ActionDelegatedEventHandler<>) &&
                o2Type.IsGenericType &&
                o2Type.GetGenericTypeDefinition() == typeof(ActionDelegatedEventHandler<>))
                return o1.Equals(o2);
            return o1Type == o2Type;
        };

        #endregion 判断事件处理程序是否相同

        #region 订阅

        #region 订阅事件列表

        public void Subscribe<TEvent>(IEventHandler<TEvent> eventHandler)
          where TEvent : class
        {
            lock (locObj)
            {
                var eventType = typeof(TEvent);
                if (dicEvents.ContainsKey(eventType))
                {
                    var handlers = dicEvents[eventType];
                    if (handlers != null)
                    {
                        if (!handlers.Exists(deh => eventHandlerEquals(deh, eventHandler)))
                            handlers.Add(eventHandler);
                    }
                    else
                    {
                        handlers = new List<object>();
                        handlers.Add(eventHandler);
                    }
                }
                else
                    dicEvents.Add(eventType, new List<object> { eventHandler });
            }
        }

        #endregion 订阅事件列表

        #region 订阅事件实体

        public void Subscribe<TEvent>(Action<TEvent> eventHandlerFunc)
           where TEvent : class
        {
            Subscribe(new ActionDelegatedEventHandler<TEvent>(eventHandlerFunc));
        }

        #endregion 订阅事件实体

        #region 订阅多个事件实体

        public void Subscribe<TEvent>(IEnumerable<IEventHandler<TEvent>> eventHandlers)
        where TEvent : class
        {
            foreach (var eventHandler in eventHandlers)
                Subscribe<TEvent>(eventHandler);
        }

        #endregion 订阅多个事件实体

        #endregion 订阅

        #region 取消订阅事件

        public void Unsubscribe<TEvent>(Action<TEvent> eventHandlerFunc)
        where TEvent : class
        {
            Unsubscribe<TEvent>(new ActionDelegatedEventHandler<TEvent>(eventHandlerFunc));
        }

        #region 取消订阅

        public void Unsubscribe<TEvent>(IEventHandler<TEvent> eventHandler)
            where TEvent : class
        {
            lock (locObj)
            {
                var eventType = typeof(TEvent);
                if (dicEvents.ContainsKey(eventType))
                {
                    var handlers = dicEvents[eventType];
                    if (handlers != null
                        && handlers.Exists(deh => eventHandlerEquals(deh, eventHandler)))
                    {
                        var handlerToRemove = handlers.First(deh => eventHandlerEquals(deh, eventHandler));
                        handlers.Remove(handlerToRemove);
                    }
                }
            }
        }

        #endregion 取消订阅

        #region 取消多个订阅事件

        public void Unsubscribe<TEvent>(IEnumerable<IEventHandler<TEvent>> eventHandlers)
        where TEvent : class
        {
            foreach (var eventHandler in eventHandlers)
                Unsubscribe<TEvent>(eventHandler);
        }

        #endregion 取消多个订阅事件

        #endregion 取消订阅事件

        #region 发布

        #region 异步发布

        public void Publish<TEvent>(TEvent evnt)
           where TEvent : class
        {
            if (evnt == null)
                throw new ArgumentNullException("事件源不能为空");
            var eventType = evnt.GetType();
            if (dicEvents.ContainsKey(eventType)
                && dicEvents[eventType] != null
                && dicEvents[eventType].Count > 0)
            {
                var handlers = dicEvents[eventType];
                foreach (var handler in handlers)
                {
                    var eventHandler = handler as IEventHandler<TEvent>;
                    eventHandler.Handler(evnt);
                }
            }
        }

        #endregion 异步发布

        #region 有回调的发布

        public void Publish<TEvent>(TEvent evnt, Action<TEvent, bool, Exception> callback, TimeSpan? timeout = null)
           where TEvent : class
        {
            if (evnt == null)
                throw new ArgumentNullException("事件源不能为空!");
            var eventType = evnt.GetType();
            if (dicEvents.ContainsKey(eventType) &&
                dicEvents[eventType] != null &&
                dicEvents[eventType].Count > 0)
            {
                var handlers = dicEvents[eventType];
                List<Task> tasks = new List<Task>();
                try
                {
                    foreach (var handler in handlers)
                    {
                        var eventHandler = handler as IEventHandler<TEvent>;
                        tasks.Add(Task.Factory.StartNew((o) => eventHandler.Handler((TEvent)o), evnt));
                    }
                    if (tasks.Count > 0)
                    {
                        if (timeout == null)
                            Task.WaitAll(tasks.ToArray());
                        else
                            Task.WaitAll(tasks.ToArray(), timeout.Value);
                    }
                    callback(evnt, true, null);
                }
                catch (Exception ex) when (ex.Log()) { }
                catch (Exception ex)
                {
                    callback(evnt, false, ex);
                }
            }
            else
                callback(evnt, false, null);
        }

        #endregion 有回调的发布

        #endregion 发布

        #endregion 方法
    }
}

using System;

namespace Util.EventBus.Obj
{
    public class ActionDelegatedEventHandler<TEvent> : IEventHandler<TEvent>
    {
        #region 构造函数

        public ActionDelegatedEventHandler(Action<TEvent> eventHandle)
        {
            action = eventHandle;
        }

        #endregion 构造函数

        #region 变量

        private Action<TEvent> action; // 处理程序

        #endregion 变量

        #region 方法

        public void Handler(TEvent t)
        {
            action?.Invoke(t);
        }

        #endregion 方法
    }
}

namespace Util.EventBus.Obj
{
    /// </summary>
    public interface IEventHandler<TEvent>
    {
        void Handler(TEvent t);
    }
}
  // 1. 订阅

        public void SubjectObj()
        {
            EventBus.Instance.Subscribe<NoticeEntity>(m =>
            {
                Console.WriteLine($"接收到消息:m");
            });
        }

        // 2. 发布
        public void PublishOjb()
        {
            EventBus.Instance.Publish(new NoticeEntity());
        }

 

  

  • 第三方组件PubSub

  第三方组件PubSub,支持观察者模式,使用Nuget导入:

 

使用如下:  // 发布

        public void SubscribePubHub(Action<string> action)
        {
            Hub.Default.Subscribe(action);
        }

        // 订阅
        public void PublishPubHub(string t)
        {
            if (t == null) return;
            Hub.Default.PublishAsync(t);
}

  

  • 第三方组件RX

第三方组件RX,支持观察者模式,RX基于线程池优化,速率最快,使用Nuget导入:

使用如下:

#region 发布

        public void Publish(T t)
        {
            try
            {
                if (!Sub.IsDisposed)
                    Sub.OnNext(t);
            }
            catch (Exception ex) when (ex.Log()) { }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        #endregion 发布

        #region 订阅

        public void Subject(Action<T> action, Action<Exception> errorAction = null, Action completeAction = null)
        {
            #region 校验

            if (action == null) throw new ArgumentException($"订阅响应回调不能为空!");

            #endregion 校验

            try
            {
                if (!Sub.IsDisposed)
                {
                    Action<T> asyncAction = t => { Task.Factory.StartNew(() => action?.Invoke(t)); };
                    Sub.SubscribeOn(TaskPoolScheduler.Default).Subscribe(action, (ex) => { errorAction?.Invoke(ex); }, () => { completeAction?.Invoke(); });
                }
            }
            catch (Exception ex) when (ex.Log()) { }
            catch
            {
                throw;
            }
        }

        #endregion 订阅

  

三 技术模式

上述四种方式测试测试1000000条字符串数据的通知完成时间,效果如下:

所以目前,选择使用RX封装的程序内通知机制。

四 存在问题

五 总结

 

 

posted @ 2021-03-11 11:20  慢慢zero  阅读(2365)  评论(0)    收藏  举报