EventAggregator, EventBus的实现

目录

系列主题:基于消息的软件架构模型演变

系列主题:基于消息的软件架构模型演变

 

.net中事件模型很优雅的实现了观察者模式,同时被大量的使用在各种框架中。如果我们非要给事件模型挑毛病,我觉得有两点:

  • 实现起来略微繁琐
  • 正如我们上篇文章分析,事件模型在特定的情况下会发生内存泄漏

于是我们想到了更加简单易用的模型:EventAggregator,正如该名称所描述,EventAggregator将观察者都聚合在一个容器里,向EventAggregator发布一个主题,EventAggregator会找到对该主题感兴趣的观察者并通知他。

IC245765

Prism框架中实现了一个典型的EventAggregator,有时候我们又把此类实现叫做EventBus。

MVVM Light中实现了一个叫Messenger的bus,Messenger是EventBus、EventAggregator等概念的抽象,因为此时的主题已经表现的像是消息一样,所以Messenger这一名称也更加靠近基于消息架构这一主题。

一、实现一个简单的EventBus

EventBus的职责有两点:

  • 注册观察者
  • 发布主题

所以接口定义为:

1
2
3
4
5
public interface ISimpleEventBus
{
    void Register<TEvent>(Action<TEvent> action);
    void Publish<TEvent>(TEvent @event);
}

所有的主题都可以用Action<TEvent> action来表示,实现起来也很简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class SimpleEventBus
{
    public static ConcurrentDictionary<Type,List<Action<object>>> Dictionary=new ConcurrentDictionary<Type, List<Action<object>>>();
 
    public void Register<TEvent>(Action<TEvent> action)
    {
        List<Action<object>> actionList;
        if (!Dictionary.TryGetValue(typeof (TEvent), out actionList))
        {
            actionList=new List<Action<object>>();
            Dictionary[typeof (TEvent)] = actionList;
        }
        actionList.Add(o=>action((TEvent)o));
    }
 
    public void Publish<TEvent>(TEvent @event)
    {
        List<Action<object>> actionList;
        if (Dictionary.TryGetValue(typeof (TEvent), out actionList))
        {
            foreach (var action in actionList)
            {
                action(@event);
            }
        }
    }
}

EventBus内部通过一个类型为ConcurrentDictionary<Type,List<Action<object>>> 的字典来存储主题和观察者列表。写个测试试试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[Test]
public void Should_handle_registered_action()
{
    var eventBus = new SimpleEventBus();
 
    var number = 0;
    eventBus.Register<MessageA>(m=>number=m.Number);
    eventBus.Publish(new MessageA(2));
 
    number.Should().Be(2);
}
 
internal class MessageA
{
    public MessageA(int number)
    {
        Number = number;
    }
    public int Number { get; private set; }
}

我们自己写的这个simpleEventBus已经能够应付大部分情况了,使用起来也比事件模型简单很多。但是仍然没有解决内存泄漏的问题。

二、MVVM Light中Messenger的实现

Messenger的实现是我们这个简单eventBus的升级版,首先他抽象了概念,从事件到消息是思维的一个转变,Messenger认为所有注册的主题都是消息。其次采用WeakReference来关联观察者和主题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public void Register<TMessage>(object recipient, Action<TMessage> action)
{
    lock (_registerLock)
    {
        var messageType = typeof(TMessage);
 
        Dictionary<Type, List<WeakAction>> recipients;
        
            if (_recipientsStrictAction == null)
            {
                _recipientsStrictAction = new Dictionary<Type, List<WeakAction>>();
            }
 
            recipients = _recipientsStrictAction;
 
        lock (recipients)
        {
            List<WeakAction> list;
 
            if (!recipients.ContainsKey(messageType))
            {
                list = new List<WeakAction>();
                recipients.Add(messageType, list);
            }
            else
            {
                list = recipients[messageType];
            }
 
            var weakAction = new WeakAction<TMessage>(recipient, action);
            
            list.Add(weakAction);
        }
    }
 
    RequestCleanup();
}

这个实现跟我们实现的EventBus大同小异,不同之处是dictionary类型为Dictionary<Type, List<WeakAction>>。

WeakAction的构造函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public WeakAction(object target, Action<T> action)
{
 
    if (action.Method.IsStatic)
    {
        _staticAction = action;
 
        if (target != null)
        {
            Reference = new WeakReference(target);
        }
 
        return;
    }
 
 
    Method = action.Method;
    ActionReference = new WeakReference(action.Target);
    Reference = new WeakReference(target);
}

ActionReference = new WeakReference(action.Target); 这句话将一个object包装进了WeakReference。

发送消息的代码也跟我们自己实现的EventBus大同小异,大家可以直接看代码对比。

写一个测试看看如何使用Messenger:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[Test]
public void Should_handle_registered_actions()
{
    int number = 0;
    Messenger.Default.Register<MessageA>(this,m=>number=m.Number);
    Messenger.Default.Send(new MessageA(2));
 
    number.Should().Be(2);
}
 
internal class MessageA
{
    public MessageA(int number)
    {
        Number = number;
    }
    public int Number { get; private set; }
}

我们注意到Messenger采用了消息的概念,所以发布主题也将方法名从publish改为了send。一般我们都说发布一个事件,发送一个消息。Messenger所提到的概念已经快要接近ServiceBus了。

posted @   richiezhang  阅读(2233)  评论(0编辑  收藏  举报
编辑推荐:
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· 展开说说关于C#中ORM框架的用法!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
点击右上角即可分享
微信分享提示