一、问题背景

  最近离职来到了一家新的公司,原先是在乙方工作,这回到了甲方,在这一个月中,发现目前的业务很大一部分是靠轮询实现的,例如:通过轮询判断数据处于B状态了,则轮询到数据后执行某种动作,这个其实是非常浪费的,并且对于数据的实时性也会不怎么友好,基于以上的情况,在某天开车堵车时候,想到了之前偶然了解过的事件总线(EventBus),对比了公司当前的场景后,觉得事件总线应该是可以满足需求的(PS:只是我觉得这个有问题,很多人不觉得有问题),那既然想到了,那就想自己是否可以做个事件总线的轮子

二、什么是事件总线

  我们知道事件是由一个Publisher跟一个或多个的Subsriber组成,但是在实际的使用过程中,我们会发现,Subsriber必须知道Publisher是谁才可以注册事件,进而达到目的,那这其实就是一种耦合,为了解决这个问题,就出现了事件总线的模式,事件总线允许不同的模块之间进行彼此通信而又不需要相互依赖,如下图所示,通过EventBus,让Publisher以及Subsriber都只需要对事件源(EventData)进行关注,不用管Publisher是谁,那么EventBus主要是做了一些什么事呢?

三、EventBus做了什么事?

  1、EventBus实现了对于事件的注册以及取消注册的管理

  2、EventBus内部维护了一份事件源与事件处理程序的对应关系,并且通过这个对应关系在事件发布的时候可以找到对应的处理程序去执行

  3、EventBus应该要支持默认就注册事件源与处理程序的关系,而不需要开发人员手动去注册(这里可以让开发人员去控制自动还是手动)

四、具体实现思路

  首先在事件总线中,存在注册、取消注册以及触发事件这三种行为,所以我们可以将这三种行为抽象一个接口出来,最终的接口代码如下:

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks; namespace MEventBus.Core
{
public interface IEventBus
{
#region 接口注册
void Register<TEventData>(Type handlerType) where TEventData : IEventData;
void Register(Type eventType, Type handlerType);
void Register(string eventType, Type handlerType);
#endregion #region 接口取消注册
void Unregister<TEventData>(Type handler) where TEventData : IEventData;
void Unregister(Type eventType, Type handlerType);
void Unregister(string eventType, Type handlerType);
#endregion void Trigger(string pubKey, IEventData eventData);
Task TriggerAsync(string pubKey, IEventData eventData);
Task TriggerAsync<TEventData>(TEventData eventData) where TEventData : IEventData;
void Trigger<TEventData>(TEventData eventData) where TEventData : IEventData;
}
}

  在以上代码中发现有些方法是有IEventData约束的,这边IEventData就是约束入参行为,原则上规定,每次触发的EventData都需要继承IEventData,而注册的行为也是直接跟入参类型相关,具体代码如下:

using System;
using System.Collections.Generic;
using System.Text; namespace MEventBus.Core
{
public interface IEventData
{
string Id { get; set; }
DateTime EventTime { get; set; }
object EventSource { get; set; }
}
}

  接下来我们看下具体的实现代码

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks; namespace MEventBus.Core
{
public class EventBus : IEventBus
{
private static ConcurrentDictionary<string, List<Type>> dicEvent = new ConcurrentDictionary<string, List<Type>>();
private IResolve _iresolve { get; set; }
public EventBus(IResolve resolve)
{
_iresolve = resolve;
InitRegister();
} public void InitRegister()
{
if (dicEvent.Count > 0)
{
return;
}
//_iresolve = ioc_container;
dicEvent = new ConcurrentDictionary<string, List<Type>>();
//自动扫描类型并且注册
foreach (var file in Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory, "*.dll"))
{
var ass = Assembly.LoadFrom(file);
foreach (var item in ass.GetTypes().Where(p => p.GetInterfaces().Contains(typeof(IEventHandler))))
{
if (item.IsClass)
{
foreach (var item1 in item.GetInterfaces())
{
foreach (var item2 in item1.GetGenericArguments())
{
if (item2.GetInterfaces().Contains(typeof(IEventData)))
{
Register(item2, item);
}
}
}
}
}
}
}
//注册以及取消注册的时候需要加锁处理
private static readonly object obj = new object(); #region 注册事件
public void Register<TEventData>(Type handlerType) where TEventData : IEventData
{
//将数据存储到mapDic
var dataType = typeof(TEventData).FullName;
Register(dataType, handlerType);
}
public void Register(Type eventType, Type handlerType)
{
var dataType = eventType.FullName;
Register(dataType, handlerType);
}
public void Register(string pubKey, Type handlerType)
{
lock (obj)
{
//将数据存储到dicEvent
if (dicEvent.Keys.Contains(pubKey) == false)
{
dicEvent[pubKey] = new List<Type>();
}
if (dicEvent[pubKey].Exists(p => p.GetType() == handlerType) == false)
{
//IEventHandler obj = Activator.CreateInstance(handlerType) as IEventHandler;
dicEvent[pubKey].Add(handlerType);
}
}
} #endregion #region 取消事件注册
public void Unregister<TEventData>(Type handler) where TEventData : IEventData
{
var dataType = typeof(TEventData);
Unregister(dataType, handler);
} public void Unregister(Type eventType, Type handlerType)
{
string _key = eventType.FullName;
Unregister(_key, handlerType);
}
public void Unregister(string eventType, Type handlerType)
{
lock (obj)
{
if (dicEvent.Keys.Contains(eventType))
{
if (dicEvent[eventType].Exists(p => p.GetType() == handlerType))
{
dicEvent[eventType].Remove(dicEvent[eventType].Find(p => p.GetType() == handlerType));
}
}
}
}
#endregion #region Trigger触发
//trigger时候需要记录到数据库
public void Trigger<TEventData>(TEventData eventData) where TEventData : IEventData
{
var dataType = eventData.GetType().FullName;
//获取当前的EventData绑定的所有Handler
Notify(dataType, eventData);
} public void Trigger(string pubKey, IEventData eventData)
{
//获取当前的EventData绑定的所有Handler
Notify(pubKey, eventData);
}
public async Task TriggerAsync<TEventData>(TEventData eventData) where TEventData : IEventData
{
await Task.Factory.StartNew(new Action(()=>
{
var dataType = eventData.GetType().FullName;
Notify(dataType, eventData);
}));
}
public async Task TriggerAsync(string pubKey, IEventData eventData)
{
await Task.Factory.StartNew(new Action(() =>
{
var dataType = eventData.GetType().FullName;
Notify(pubKey, eventData);
}));
}
//通知每成功执行一个就需要记录到数据库
private void Notify<TEventData>(string eventType, TEventData eventData) where TEventData : IEventData
{
//获取当前的EventData绑定的所有Handler
var handlerTypes = dicEvent[eventType];
foreach (var handlerType in handlerTypes)
{
var resolveObj = _iresolve.Resolve(handlerType);
IEventHandler<TEventData> handler = resolveObj as IEventHandler<TEventData>;
handler.Handle(eventData); }
}
#endregion
}
}

  代码说明:

  1、如上的EventBus是继承了IEventBus后的具体实现,小伙伴可能看到在构造函数里,有一个接口参数IResolve,这个主要是为了将解析的过程进行解耦,由于在一些WebApi的项目中,更加多的是使用IOC的机制进行对象的创建,那基于IResolve就可以实现不同的对象创建方式(内置的是通过反射实现)

  2、InitRegister方法通过遍历当前目录下的dll文件,去寻找所有实现了IEventHandler<IEventData>接口的信息,并且自动注册到EventBus中,所以在实际使用过程中,应该是没有机会去适用register注册的

  3、触发机制实现了同步以及异步的调用,这个从方法命名中就可以看出来

五、程序Demo

  TestHandler2(继承IEventHandler)

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using MEventBus.Core; namespace MEventBusHandler.Test
{
public class TestHandler2 : IEventHandler<TestEventData>
{
public void Handle(TestEventData eventData)
{
Thread.Sleep(2000);
MessageBox.Show(eventData.EventTime.ToString());
}
}
}

  TestEventData(继承EventData,EventData是继承了IEventData的代码)

using MEventBus.Core;
using System;
using System.Collections.Generic;
using System.Text; namespace MEventBusHandler.Test
{
public class TestEventData : EventData
{ }
}

  调用代码

using MEventBus.Core;
using MEventBusHandler.Test;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms; namespace MEventBus.Test
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
TestHandler.OnOut += TestHandler_OnOut;
} private void TestHandler_OnOut(object sender, EventArgs e)
{
MessageBox.Show("Hello World");
} private void button1_Click(object sender, EventArgs e)
{
var task = new MEventBus.Core.EventBus(new ReflectResolve()).TriggerAsync(new TestEventData());
task.ContinueWith((obj) => {
MessageBox.Show("事情全部做完");
});
} private void button2_Click(object sender, EventArgs e)
{
new EventBus(new ReflectResolve()).Trigger(new TestEventData());
}
} }

  执行结果

我在真正的Demo中,其实是注册了2个handler,可以在后续公布的项目地址里看到

六、总结

  从有这个想法开始,到最终实现这个事件总线,大概总共花了2,3天的时间(PS:晚上回家独自默默干活),目前只能说是有一个初步可以使用的版本,并且还存在着一些问题:

  1、在.NetFrameWork下(目前公司还不想升级到.NetCore,吐血。。),如果使用AutoFac创建EventBus(单例模式下),如果Handler也使用AutoFac进行创建,会出现要么对象创建失败,要么handler里的对象与调用方的对象不是同一个实例,为了解决这个问题,我让EventBus不再是单例模式,将dicEvent变成了静态,暂时表面解决

  2、未考虑跨进程的实现(感觉用savorboard大佬的CAP就可以了)

  3、目前这个东西在一个小的新项目里使用,暂时在测试环境还算没啥问题,各位小伙伴如果有类似需求,可以做个参考

  由于个人原因,在测试上可能会有所不够,如果有什么bug的话,还请站内信告知,感谢(ps:文字表达弱鸡,技术渣渣,各位多多包涵)

  最后:附上项目地址:https://gitee.com/OneMango/MEventBus

 

作者: Mango

出处: http://www.cnblogs.com/OMango/

关于自己:专注.Net桌面开发以及Web后台开发,对.NetCore、微服务、DevOps,K8S等感兴趣,最近到了个甲方公司准备休养一段时间

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接,如有问题, 可站内信告知.

 

基于.NetStandard的简易EventBus实现-基础实现的更多相关文章

  1. 基于redis的简易分布式爬虫框架

    代码地址如下:http://www.demodashi.com/demo/13338.html 开发环境 Python 3.6 Requests Redis 3.2.100 Pycharm(非必需,但 ...

  2. 基于RxJava2+Retrofit2精心打造的Android基础框架

    代码地址如下:http://www.demodashi.com/demo/12132.html XSnow 基于RxJava2+Retrofit2精心打造的Android基础框架,包含网络.上传.下载 ...

  3. Android消息传递之基于RxJava实现一个EventBus - RxBus

    前言: 上篇文章学习了Android事件总线管理开源框架EventBus,EventBus的出现大大降低了开发成本以及开发难度,今天我们就利用目前大红大紫的RxJava来实现一下类似EventBus事 ...

  4. 面向服务体系架构(SOA)和数据仓库(DW)的思考基于 IBM 产品体系搭建基于 SOA 和 DW 的企业基础架构平台

    面向服务体系架构(SOA)和数据仓库(DW)的思考 基于 IBM 产品体系搭建基于 SOA 和 DW 的企业基础架构平台 当前业界对面向服务体系架构(SOA)和数据仓库(Data Warehouse, ...

  5. 基于Android 平台简易即时通讯的研究与设计[转]

    摘要:论文简单介绍Android 平台的特性,主要阐述了基于Android 平台简易即时通讯(IM)的作用和功能以及实现方法.(复杂的通讯如引入视频音频等可以考虑AnyChat SDK~)关键词:An ...

  6. 基于.netstandard的权限控制组件

    基于.netstandard的权限控制组件 Intro 由于项目需要,需要在 基于 Asp.net mvc 的 Web 项目框架中做权限的控制,于是才有了这个权限控制组件. 项目基于 .NETStan ...

  7. 如何基于Winform开发框架或混合框架基础上进行项目的快速开发

    在开发项目的时候,我们为了提高速度和质量,往往不是白手起家,需要基于一定的基础上进行项目的快速开发,这样可以利用整个框架的生态基础模块,以及成熟统一的开发方式,可以极大提高我们开发的效率.本篇随笔就是 ...

  8. 【转】基于Map的简易记忆化缓存

    看到文章后,自己也想写一些关于这个方面的,但是觉得写的估计没有那位博主好,而且又会用到里面的许多东西,所以干脆转载.但是会在文章末尾写上自己的学习的的东西. 原文出处如下: http://www.cn ...

  9. 基于Map的简易记忆化缓存

    背景 在应用程序中,时常会碰到需要维护一个map,从中读取一些数据避免重复计算,如果还没有值则计算一下塞到map里的的小需求(没错,其实就是简易的缓存或者说实现记忆化).在公司项目里看到过有些代码中写 ...

随机推荐

  1. 关于StreamReader的知识分享

    今天我们来简单的介绍一下StreamReader,在将StreamReader之前,我们先来了解一下他的父类:TextReader.对于TextReader,大家可能比较陌生,下面我们来看一下Text ...

  2. 第一个shell脚本(一)

    第一个脚本 [root@ipha-dev71- exercise_shell]# ll total -rw-r--r-- root root Aug : test.sh [root@ipha-dev7 ...

  3. Jenkins指定tag发布到k8s环境

    Jenkins指定tag发布到k8s环境 1.Jenkins配置一个Pipeline 工程 首先要安装插件:https://www.cnblogs.com/Dev0ps/p/9125232.html ...

  4. .NET Core 3.0之深入源码理解ObjectPool(二)

    写在前面 前文主要介绍了ObjectPool的一些理论基础,本文主要从源码角度理解Microsoft.Extensions.ObjectPool是如何实现的.下图为其三大核心组件图: 核心组件 Obj ...

  5. 学习python3高阶函数笔记和demo

    python的高阶函数的定义是:一个函数接收另一个函数作为参数,这种函数就称之为高阶函数 举一个最简单的例子: def text(a,b,c): return c(a)+c(b) print( tex ...

  6. Creator 2.2.0 终于等来了这款Shader组件神器!一招搞定Effect特效

    先看下视频演示: ShaderHelper2支持Creator 2.2.0 视频录完后才想起,还没在微信小游戏中测试,赶紧试试,下面是在微信开发者工具中的截图. 径向模糊 探照灯 提供了一个Shade ...

  7. 一个简单的Post Get请求

    WWW请求 using System; using System.Collections; using System.Collections.Generic; using UnityEngine; u ...

  8. ORM之多表操作

    一.创建模型 from django.db import models # Create your models here. class Book(models.Model): nid = model ...

  9. [springboot 开发单体web shop] 4. Swagger生成Javadoc

    Swagger生成JavaDoc 在日常的工作中,特别是现在前后端分离模式之下,接口的提供造成了我们前后端开发人员的沟通 成本大量提升,因为沟通不到位,不及时而造成的[撕币]事件都成了日常工作.特别是 ...

  10. 还不会用FindBugs?你的代码质量很可能令人堪忧

    前言 项目中代码质量,往往需要比较有经验的程序员的审查来保证.但是随着项目越来越大,代码审查会变得越来越复杂,需要耗费越来越多的人力.而且程序员的经验和精力都是有限的,能审查出问题必定有限.而在对代码 ...