LoxodonFramework——消息系统(Messenger)

消息系统

消息系统一直是我很头痛的模块。它写起来很简单,不考虑鲁棒性的话,用字典存储委托再封装一下注册跟发送方法就好。但是每次写出来都会发现实际使用的时候总是有不舒服的点。也看过一些比较好的设计,基本上都会把消息名和参数封装成类,LF其实也是这种封装方法。但一个显著的问题就是随着消息类型变多,这些类也会越来越多,甚至作用重复了也不一定知道,这种近乎一次性的类也挺头疼的。相对的是,回调写起来会容易跟清晰一点。只有一个形参就是消息类,那就是Action<T>没跑了。

LF的消息系统是支持多线程的,一些耗时的计算可以扔给多线程,计算完之后直接发送消息到UI线程去执行回调。

IMessenger(消息系统接口)

消息系统接口主要声明了订阅(Subscribe)和发布(Publish)两个方法以及它们多渠道、泛型、泛型多渠道版本的重载。

 

Messenger(消息系统)

Messenger是IMessenger的实现类。除了实现接口方法以外还提供了一个Default的静态单例。

为了保证线程安全,Messenger使用ConcurrentDictionary而不是Dictionary来存储注册的回调。前者是后者的线程安全版本。查阅资料后的进一步补充是:ConcurrentDictionary自身提供的公有方法和受保护方法都是线程安全的,但其实现的其他接口并不是线程安全的。

这里贴出一个Subscribe方法实现来详细解析保证线程安全操作。

public virtual ISubscription<object> Subscribe(Type type, Action<object> action)
{
    SubjectBase notifier;
    if (!notifiers.TryGetValue(type, out notifier))
    {
        notifier = new Subject<object>();
        if (!notifiers.TryAdd(type, notifier))
            notifiers.TryGetValue(type, out notifier);
    }
    return (notifier as Subject<object>).Subscribe(action);
}

在尝试获取已有的notifier失败后,新实例化了一个Subject,并且再次尝试添加到字典中,此时可能由于线程切换中止了函数调用,并在另一个线程中将对应Type的key添加进了字典中。这时候线程再切换回来Add操作无疑会失败,而失败意味着已经存在了,这时候再Get一次就好。

另一个小语法点是LF似乎是以较早版本的C#进行的开发(或者是为了兼容较早版本的Unity),一些常见语法糖都没有在LF中出现。比如这里先声明了SubjectBase的notifier变量再进行的TryGet,而较新的C#都支持在传out参数时再声明为内联变量。也就是上述代码可以简化为:

if (!notifiers.TryGetValue(type, out SubjectBase notifier))
{
    //DoSomething
}

和for循环中的int i不同。out处声明的变量在当前作用域一直有效。

Messenger中字典的key是Type,对应的是MessageBase类型的子类(上面讲了LF将消息名跟参数都封装成了类,这里就应该取对应类的Type做key)。value是抽象类SubjectBase的具体子类,它负责管理一个类型的所有委托。

Subject

subject常见翻译是主题。我没找到意思和该类功能非常接近的翻译。总之就是管理同类型所有委托的。它继承自抽象类SubjectBase。Subject也没直接管理委托,而是管理的实现了ISubscription的Subscription。它也是Messenger.Subscribe的返回值,旨在提供一个链式操作来指定委托在哪个线程中执行。

Subject中也使用ConcurrentDictionary存储Subscription,但是有一个点要额外注意,就是它没有直接存储Subscription引用,而是使用WeakReference<Subscription>存储了弱引用。我在使用Lua的过程中首次理解了弱引用的作用(Lua中叫做弱表)。它和引用的区别就是持有对象的弱引用不会影响GC(可以理解为每有一个对象持有A的引用,A的引用计数就+1。而A的引用计数只要不为0就不会被GC回收。但持有A的弱引用,A的引用计数不会+1。)由此也可知弱引用很可能为空,所以使用的时候只能通过TryGetTarget来获取引用。

迷思:LF的特点是面向接口编程,而偏偏SubjectBase是只有一个抽象方法Publish的抽象类,这里明明换成接口更合适。我把抽象类该成接口之后程序也运行良好没有任何问题。那么问题来了,SubjectBase特意写成抽象类有什么意义吗?

Subscription

Subscription是Subject的内部类,实现了ISubscription接口。提供了一个ObserveOn的对外接口,接受一个SynchronizationContext(同步上下文,C#原生类)类型的参数用于跨线程调用委托。

Subscription管理一个委托,根据是否有同步上下文来选择是当前线程执行还是在SynchronizationContext对应的线程执行。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值