日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

事件总线知多少(2)

發布時間:2023/12/4 编程问答 44 豆豆
生活随笔 收集整理的這篇文章主要介紹了 事件总线知多少(2) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

1.引言

之前的一篇文章事件總線知多少(1),介紹了什么是事件總線,并通過發布訂閱模式一步一步的分析重構,形成了事件總線的Alpha版本,這篇文章也得到了大家的肯定和積極的反饋和建議,在此謝謝大家。本著繼續學習和回饋大家的思想,我決定繼續完善。本文將繼續延續上一篇循序漸進的寫作風格,來完成對事件總線的分析和優化。

2.回顧事件總線

在進行具體分析之前,我們還是先對我們實現的事件總線進行一個簡單的回顧:

  • 針對事件源,抽象IEventData接口;

  • 針對事件處理,抽象IEventHandler<TEventData>接口,定義唯一事件處理方法void HandleEvent(IEventData eventData);

  • 事件總線維護一個事件源和事件處理的類型映射字典ConcurrentDictionary<Type, List<Type>> _eventAndHandlerMapping;

  • 通過單例模式,確保事件總線的唯一入口;

  • 利用反射完成事件源與事件處理的動態初始化綁定;

  • 提供入口支持事件的手動注冊/取消注冊;

  • 提供統一的事件觸發接口,通過反射動態創建IEventHandler實例完成具體事件處理邏輯的調用。

  • 3.發現反射問題

    基于以上的簡單回顧,我們可以發現Alpha版本事件總線的成功離不開反射的支持。從動態綁定到動態觸發,都是反射在默默的處理著業務邏輯。如果我們只是簡單學習了解事件總線,使用反射無可厚非。但如果在實際的項目中,使用反射卻不是一個很明智的行為,因為其性能問題。尤其是事件總線要集中處理整個應用程序的所有事件,更易導致程序性能瓶頸。
    既然說到了反射性能,那就順便解釋下為什么反射性能差?

  • 類型綁定(元數據字符串匹配)

  • 參數校驗

  • 安全校驗

  • 基于運行時

  • 反射產生大量臨時對象,增加GC負擔

  • 那既然反射有性能瓶頸,我們該如何是好呢?
    你可能會說,既然反射有問題,那就對反射進行性能優化,比如增加緩存機制。出發點是好的,但最終還是在反射問題的陰影之下。對于反射我們應該持以這樣一種態度:能不用反射,則不用反射。

    那既然要推翻反射這條路,那如何解決動態綁定和動態觸發的問題呢?
    辦法總比問題多。額,啊,嗯。就不饒圈子了,咱們上IOC。

    4.使用IOC解除依賴

    先看下面一張圖,來了解下DIP、IOC、DI與SL之間的關系,詳細可參考Asp.net mvc 知多少(十)。

    下面我們就以Castle Windsor作為我們的IOC容器為例,來講解下如何解除依賴。

    4.1. 了解Castle Windsor

    使用Castle Windsor主要包含以下幾步:

  • 初始化容器:var container = new WindsorContainer();

  • 使用WindsorInstallers從執行程序集添加和配置所有組件:container.Install(FromAssembly.This());

  • 實現IWindsorInstaller自定義安裝器:

    public class RepositoriesInstaller : IWindsorInstaller{public void Install(IWindsorContainer container, IConfigurationStore store){container.Register(Classes.FromThisAssembly().Where(Component.IsInSameNamespaceAs<King>()).WithService.DefaultInterfaces().LifestyleTransient()); } }
  • 注冊和解析依賴

  • 程序退出時,釋放容器

  • 4.2. 使用Castle Windsor

    使用IOC容器的目的很明確,一個是在注冊事件時完成依賴的注入,一個是在觸發事件時完成依賴的解析。從而完成事件的動態綁定和觸發。

    4.2.1. 初始化容器

    要在EventBus這個類中完成事件依賴的注入和解析,就需要在本類中持有一個對IWindsorContainer的引用。
    可以直接定義一個只讀屬性,并在構造函數中進行初始化即可。

    public IWindsorContainer IocContainer { get; private set; }
    //定義IOC容器

    private readonly ConcurrentDictionary<Type, List<Type>> _eventAndHandlerMapping;public EventBus(){IocContainer = new WindsorContainer();_eventAndHandlerMapping = new ConcurrentDictionary<Type, List<Type>>(); }

    4.2.2.注冊和取消注冊依賴

    初始化完容器,我們需要在手動注冊和取消注冊事件API上分別完成依賴的注冊和取消注冊。因為Castle Windsor在3.0版本取消了UnRegister方法,所以在進行事件注冊時,就不再手動卸載IOC容器中已注冊的依賴。

    /// <summary>
    /// 手動綁定事件源與事件處理
    /// </summary>
    /// <param name="eventType"></param>
    /// <param name="handlerType"></param>public void Register(Type eventType, Type handlerType) { ?
    ? //注冊IEventHandler<T>到IOC容器var handlerInterface = handlerType.GetInterface("IEventHandler`1"); ? ? if (!IocContainer.Kernel.HasComponent(handlerInterface)){IocContainer.Register(Component.For(handlerInterface, handlerType));} ? ?
    ? ?//注冊到事件總線//省略其他代碼}
    /// <summary>
    /// 手動解除事件源與事件處理的綁定
    /// </summary>
    /// <typeparam name="TEventData"></typeparam>
    /// <param name="handlerType"></param>
    public void UnRegister<TEventData>(Type handlerType) {_eventAndHandlerMapping.GetOrAdd(typeof(TEventData), (type) => new List<Type>()).RemoveAll(t => t == handlerType); }

    4.2.3. 動態事件綁定

    要實現事件的動態綁定,我們要拿到所有IEventHandler<T>的實現。而遍歷所有類型最好的辦法就是拿到程序集(Assembly)。拿到程序集后就可以將所有IEventHandler<T>的實現注冊到IOC容器,然后再基于IOC容器注冊的IEventHandler<T>動態映射事件源和事件處理。

    /// <summary>
    /// 提供入口支持注冊其它程序集中實現的IEventHandler
    /// </summary>
    /// <param name="assembly"></param>
    public void RegisterAllEventHandlerFromAssembly(Assembly assembly) { ? ?//1.將IEventHandler注冊到Ioc容器IocContainer.Register(Classes.FromAssembly(assembly).BasedOn(typeof(IEventHandler<>)).WithService.AllInterfaces().LifestyleSingleton()); ?
    ?//2.從IOC容器中獲取注冊的所有IEventHandlervar handlers = IocContainer.Kernel.GetHandlers(typeof(IEventHandler));foreach (var handler in handlers){ ? ? ? ?//循環遍歷所有的IEventHandler<T>var interfaces = handler.ComponentModel.Implementation.GetInterfaces();foreach (var @interface in interfaces){ ? ? ? ? ? ?if (!typeof(IEventHandler).IsAssignableFrom(@interface)){ ? ? ? ? ? ? ? ?continue;} ? ? ? ? ? ?//獲取泛型參數類型var genericArgs = @interface.GetGenericArguments();if (genericArgs.Length == 1){ ? ? ? ? ? ? ? ?//注冊到事件源與事件處理的映射字典中Register(genericArgs[0], handler.ComponentModel.Implementation);}}} }

    通過這種方式,我們就可以再其他需要使用事件總線的項目中,添加引用后,通過調用以下代碼,來完成程序集中IEventHandler<T>的動態綁定。

    //注冊當前程序集中實現的所有IEventHandler<T>EventBus.Default.RegisterAllEventHandlerFromAssembly(Assembly.GetExecutingAssembly());

    4.2.4. 動態事件觸發

    觸發事件時主要分三步,第一步從事件源與事件處理的字典中取出映射的IEventHandler集合,第二步使用IOC容器解析依賴,第三步調用HandleEvent方法。代碼如下:

    /// <summary>
    /
    // 根據事件源觸發綁定的事件處理
    /// </summary>
    /// <typeparam name="TEventData"></typeparam>
    /// <param name="eventData"></param>
    public void Trigger<TEventData>(TEventData eventData) where TEventData : IEventData { ? ?//獲取所有映射的EventHandlerList<Type> handlerTypes = _eventAndHandlerMapping[typeof(TEventData)]; ? ?if (handlerTypes != null && handlerTypes.Count > 0){ ? ? ? ?foreach (var handlerType in handlerTypes){ ? ? ? ? ? ?//從Ioc容器中獲取所有的實例var handlerInterface = handlerType.GetInterface("IEventHandler`1"); ? ?
    ? ? ? ?var eventHandlers = IocContainer.ResolveAll(handlerInterface); ? ? ? ? ? ?//循環遍歷,僅當解析的實例類型與映射字典中事件處理類型一致時,才觸發事件foreach (var eventHandler in eventHandlers){ ? ? ? ? ? ?
    ? ? ? ? ? ?? ?if (eventHandler.GetType() == handlerType){ ? ? ? ? ? ? ? ?
    ? ? ? ?? ?? ? ? ??var handler = eventHandler as IEventHandler<TEventData>;handler.HandleEvent(eventData);}}}} }

    5.用例完善

    我們上面使用IOC容器替換了反射,在程序的易用性和性能上都有所提升。但很顯然,用例不夠完善且存在一些潛在問題,比如:

  • 支持Action EventHandler的綁定和觸發

  • 異步觸發

  • 觸發指定的EventHandler

  • 線程安全

  • 等等等

  • 下面我們就來先一一完善以上幾個問題。

    5.1.支持Action事件處理器

    如果每一個事件處理都要定義一個類去實現IEventHandler<T>接口,很顯然會造成類急劇膨脹。且在一些簡單場景,定義一個類又大才小用。這時我們應該立刻想到Action。
    使用Action,第一步我們要對其進行封裝,提供一個公共的ActionEventHandler來統一處理所有的Action事件處理器。代碼如下:

    /// <summary>
    /
    // 支持Action的事件處理器
    /// </summary>
    /// <typeparam name="TEventData"></typeparam>
    internal class ActionEventHandler<TEventData> : IEventHandler<TEventData> where TEventData : IEventData { ? ?/// <summary>/// 定義Action的引用,并通過構造函數傳參初始化/// </summary>public Action<TEventData> Action { get; private set; } ? ?public ActionEventHandler(Action<TEventData> handler) ? ?{Action = handler;} ? ?/// <summary>/// 調用具體的Action來處理事件邏輯/// </summary>/// <param name="eventData"></param>public void HandleEvent(TEventData eventData) ? ?{Action(eventData);} }

    有了ActionEventHandler做封裝,下一步就是注入IOC容器并注冊到事件總線了。

    /// <summary>/// 注冊Action事件處理器/// </summary>/// <typeparam name="TEventData"></typeparam>/// <param name="action"></param>public void Register<TEventData>(Action<TEventData> action) where TEventData : IEventData{ ? ? //1.構造ActionEventHandlervar actionHandler = new ActionEventHandler<TEventData>(action); ? ? //2.將ActionEventHandler的實例注入到Ioc容器IocContainer.Register(Component.For<IEventHandler<TEventData>>().UsingFactoryMethod(() => actionHandler).LifestyleSingleton()); ? ? //3.注冊到事件總線Register<TEventData>(actionHandler);}

    使用起來就很簡單:

    //注冊Action事件處理器EventBus.Default.Register<EventData>(actionEventData =>{Trace.TraceInformation(actionEventData.EventTime.ToLongDateString());});//觸發EventBus.Default.Trigger(new EventData());

    5.2. 支持異步觸發

    異步觸發很簡單直接使用Task.Run包裝一下就ok了。

    /// <summary>
    /// 異步觸發
    /// </summary>
    /// <typeparam name="TEventData"></typeparam>
    /// <param name="eventData"></param>
    /// <returns></returns>
    public Task TriggerAsync<TEventData>(TEventData eventData) where TEventData : IEventData { ? ?return Task.Run(() => Trigger<TEventData>(eventData)); }

    5.3.觸發指定EventHandler

    在我們的Trigger方法中我們會將某一個事件源綁定的事件處理全部觸發。但在某些場景下,我們可能并不需要全部觸發,僅需要觸發指定的EventHandler。這個需求很實際,我們來實現一下。

    /// <summary>
    /// 觸發指定EventHandler
    /// </summary>
    /// <param name="eventHandlerType"></param>
    /// <param name="eventData"></param>
    public void Trigger<TEventData>(Type eventHandlerType, TEventData eventData) where TEventData : IEventData { ?
    ?//獲取類型實現的泛型接口var handlerInterface = eventHandlerType.GetInterface("IEventHandler`1"); ? ?var eventHandlers = IocContainer.ResolveAll(handlerInterface);
    ?? ?//循環遍歷,僅當解析的實例類型與映射字典中事件處理類型一致時,才觸發事件foreach (var eventHandler in eventHandlers){ ? ? ? ?if (eventHandler.GetType() == eventHandlerType){ ? ? ? ? ? ?var handler = eventHandler as IEventHandler<TEventData>;handler?.HandleEvent(eventData);}} }
    /// <summary>
    /// 異步觸發指定EventHandler
    /// </summary>
    /// <param name="eventHandlerType"></param>
    /// <param name="eventData"></param>
    /// <returns></returns>
    public Task TriggerAsycn<TEventData>(Type eventHandlerType, TEventData eventData) ? ?where TEventData : IEventData { ? ?return Task.Run(() => Trigger(eventHandlerType, eventData)); }

    上個測試用例:

    [Fact]
    public async void Should_Call_Specified_Handler_Async(){TestEventBus.Register<TestEventData>(new TestEventHandler()); ?
    ?var count = 0;TestEventBus.Register<TestEventData>(actionEventData => { count++; }); ? ?await TestEventBus.TriggerAsycn<TestEventData>(typeof(TestEventHandler), new TestEventData(999));TestEventHandler.TestValue.ShouldBe(999);count.ShouldBe(0); }

    5.4.線程安全問題

    在事件總線中,維護的事件源和事件處理的映射字典是整個程序中的重中之重。我們選擇了使用ConcurrentDictionary線程安全字典來規避線程安全問題。但實際我們真正做到線程安全了嗎?我們看下映射字典申明:

    ? ? ? ?/// <summary>/// 定義線程安全集合/// </summary>private readonly ConcurrentDictionary<Type, List<Type>> _eventAndHandlerMapping;

    聰慧如你,我們的事件源支持綁定多個事件處理,ConcurrentDictionary確保了對key值(事件源)修改的線程安全,但無法確保事件處理的列表List<Type>的線程安全。那我們就來動手改造吧。同樣代碼很簡單:

    /// <summary>
    /// 定義鎖對象///
    </summary>
    p
    rivate static object lockObj= new object();
    /// <summary>
    /// 獲取事件總線映射字典中指定事件源的事件列表
    /// 若有,返回列表
    /// 若無,構造空列表返回
    /// </summary>
    /// <param name="eventType"></param>
    /// <returns></returns>
    private List<Type> GetOrCreateHandlers(Type eventType){
    ? ?return _eventAndHandlerMapping.GetOrAdd(eventType, (type) => new List<Type>()); }public void Register(Type eventType, Type handlerType){ ?
    ? ?//省略其他代碼//注冊到事件總線lock (lockObj){GetOrCreateHandlers(eventType).Add(handlerType);} }

    public
    void UnRegister<TEventData>(Type handlerType) { ? ?lock (lockObj){GetOrCreateHandlers(typeof(TEventData)).RemoveAll(t => t == handlerType);} }

    6.單元測試

    為了確保重構的正確性和業務的完整性,以上的改進都是基于單元測試進行改進的,使用的是Xunit+Shouldly。雖然不能保證單元測試的覆蓋度,但至少確保了正常業務的流轉。

    frameborder="0" scrolling="no" style="border-width: initial; border-style: none; width: 840px; height: 318px;">

    7.總結

    這一次,通過單元測試,一步一步的推進事件總線的重構和完善。主要完成了使用IOC替換反射來解耦和一些用例的完善。源碼已上傳至Github(源碼路徑:Github-EventBus)。

    至此,事件總線進入Beta版本。但很顯然還有許多細節有待完善,比如異常處理等,后續就不再繼續這個系列,我會直接維護Github的源碼,感興趣的可自行參閱。


    參考資料:
    ABP EventBus
    [c#] 反射真的很可怕嗎?

    原文地址:http://www.cnblogs.com/sheng-jie/p/7063011.html


    .NET社區新聞,深度好文,微信中搜索dotNET跨平臺或掃描二維碼關注

    總結

    以上是生活随笔為你收集整理的事件总线知多少(2)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。