企业网站建设合作合同,网站规划结构,太原seo排名优化公司,网站建设佰首选金手指二五友情提示#xff1a;阅读本文大概需要8分钟。欢迎大家点击上方公众号链接关注我#xff0c;了解新西兰码农生活本文目录#xff1a;1. 介绍2. Message - 消息3. Subscription - 订阅4. MessageHub - 消息总线4.1 Subscribe - 订阅4.2 Unsubscribe - 取消订阅4.3 Publish - 发… 友情提示阅读本文大概需要8分钟。欢迎大家点击上方公众号链接关注我了解新西兰码农生活本文目录1. 介绍2. Message - 消息3. Subscription - 订阅4. MessageHub - 消息总线4.1 Subscribe - 订阅4.2 Unsubscribe - 取消订阅4.3 Publish - 发布5. 用法5.1 从NuGet安装5.2 创建Message类5.3 订阅5.4 发布Message5.5 参数5.6 取消订阅6. 与MvvmCross.Messenger的差异1. 介绍Sub-Pub模式是一种常用的设计模式用来在系统的不同组件中传递消息。发送消息的称为Publisher接收消息的称为Subscriber。双方一般不需要知道对方的存在由一个代理负责消息的传递。其结构如图所示最初的需求是我需要开发一个实现Socket发送/接收的WPF应用程序。首先我应用MVVM模式创建了一个基本的WPF应用程序。然后我创建了另一个项目来完成所有与Socket通信有关的工作。接下来我必须将Socket项目集成到ViewModel项目中以操作Socket连接。显然我们可以为此使用Event。例如我们可以有一个名为 SocketServer的类该类具有一个事件来接收Socket数据包然后在ViewModel层中对其进行订阅。但这意味着我们必须在ViewModel层中创建 SocketServer类的实例该类将ViewModel层与Socket项目耦合在一起。我希望创建一个中间件以解耦它们。 这样发布者和订阅者就不需要知道对方的存在了。MvvmCross提供了一个名为 Messenger 的插件以在ViewModel之间进行通信。但它依赖于某些MvvmCross组件这意味着如果我想在其他项目中使用此插件则必须引用MvvmCross。这对我当前的情况而言并不理想因为实际上Socket项目没有必要引用MvvmCross。因此我做了一个专注于发布/订阅模式的项目并删除了对MvvmCross的依赖。现在可以在任何WPFUWP和Xamarin项目中重复使用它。我已将其发布到GitHub上https://github.com/yanxiaodi/CoreMessenger 并发布了NuGet包https://www.nuget.org/packages/FunCoding.CoreMessenger/。本文仅介绍该组件的实现细节后面会再写一篇文章介绍如何使用Azure DevOps实现CI/CD。下面让我们了解一下Sub-Pub模式的一种实现方式。2. Message - 消息Message是在此系统中表示消息的抽象类public abstract class Message{ public object Sender { get; private set; } protected Message(object sender) { Sender sender ?? throw new ArgumentNullException(nameof(sender)); }}我们需要从该抽象类派生不同消息的实例。它有一个名为sender的参数因此订阅者可以获取发送者的实例。但这并不是强制性的。3. Subscription - 订阅BaseSubscription是订阅的基类。代码如下 public abstract class BaseSubscription { public Guid Id { get; private set; } public SubscriptionPriority Priority { get; private set; } public string Tag { get; private set; } public abstract Taskbool Invoke(object message); protected BaseSubscription(SubscriptionPriority priority, string tag) { Id Guid.NewGuid(); Priority priority; Tag tag; } }它有一个 Id属性和一个 tag属性因此您可以放置一些标签来区分或分组订阅实例。 Priority属性是一个枚举类型用于指示订阅的优先级因此将按预期顺序调用订阅。订阅有两种类型一是强引用订阅StrongSubscriptionpublic class StrongSubscriptionTMessage : BaseSubscription where TMessage : Message { private readonly ActionTMessage _action; public StrongSubscription(ActionTMessage action, SubscriptionPriority priority, string tag): base(priority, tag) { _action action; } public override async Taskbool Invoke(object message) { var typedMessage message as TMessage; if (typedMessage null) { throw new Exception($Unexpected message {message.ToString()}); } await Task.Run(() _action?.Invoke(typedMessage)); return true; } }它继承了BaseSubscription并覆盖了Invoke()方法。基本上它具有一个名为 _action的字段该字段在创建实例时定义。当我们发布消息时订阅将调用Invoke()方法来执行该_action。我们使用Task来包装动作以便可以利用异步操作的优势。这是名为 WeakSubscription”的另一种订阅public class WeakSubscriptionTMessage : BaseSubscription where TMessage : Message{ private readonly WeakReferenceActionTMessage _weakReference; public WeakSubscription(ActionTMessage action, SubscriptionPriority priority, string tag) : base(priority, tag) { _weakReference new WeakReferenceActionTMessage(action); } public override async Taskbool Invoke(object message) { var typedMessage message as TMessage; if (typedMessage null) { throw new Exception($Unexpected message {message.ToString()}); } ActionTMessage action; if (!_weakReference.TryGetTarget(out action)) { return false; } await Task.Run(() action?.Invoke(typedMessage)); return true; }}它与强引用订阅的区别在于action存储在WeakReference字段中。您可以在这里了解更多信息WeakReference 类。它用于表示类型化的弱引用该弱引用引用一个对象同时仍允许该对象被垃圾回收回收。在使用它之前我们需要使用TryGetTarget(T)方法检查目标是否已由GC收集。如果此方法返回false则表示该引用已被GC收集。如果使用StrongSubscriptionMessenger将保留对回调方法的强引用并且Garbage Collection将不会破坏订阅。在这种情况下您需要明确取消订阅以避免内存泄漏。否则可以使用WeakSubscription当对象超出范围时会自动删除订阅。4. MessengerHub - 消息总线MessengerHub是整个应用程序域中的一个单例实例。我们不需要使用依赖注入来创建实例因为它的目的很明确我们只有一个实例。这是实现单例模式的简单方法public class MessengerHub{ private static readonly LazyMessengerHub lazy new LazyMessengerHub(() new MessengerHub()); private MessengerHub() { } public static MessengerHub Instance { get { return lazy.Value; } }}MessengerHub在其内部维护一个ConcurrentDictionary来管理订阅的实例如下所示private readonly ConcurrentDictionaryType, ConcurrentDictionaryGuid, BaseSubscription _subscriptions new ConcurrentDictionaryType, ConcurrentDictionaryGuid, BaseSubscription();该ConcurrentDictionary的Key是Message的类型Value是一个ConcurrentDictionary其中包含该特定Message的一组订阅。显然一种类型可能具有多个订阅。4.1 Subscribe - 订阅MessageHub公开了几种重要的方法来订阅/取消订阅/发布消息。Subscribe()方法如下所示 public SubscriptionToken SubscribeTMessage(ActionTMessage action, ReferenceType referenceType ReferenceType.Weak, SubscriptionPriority priority SubscriptionPriority.Normal, string tag null) where TMessage : Message { if (action null) { throw new ArgumentNullException(nameof(action)); } BaseSubscription subscription BuildSubscription(action, referenceType, priority, tag); return SubscribeInternal(action, subscription); } private SubscriptionToken SubscribeInternalTMessage(ActionTMessage action, BaseSubscription subscription) where TMessage : Message { if (!_subscriptions.TryGetValue(typeof(TMessage), out var messageSubscriptions)) { messageSubscriptions new ConcurrentDictionaryGuid, BaseSubscription(); _subscriptions[typeof(TMessage)] messageSubscriptions; } messageSubscriptions[subscription.Id] subscription; return new SubscriptionToken(subscription.Id, async () await UnsubscribeInternalTMessage(subscription.Id), action); }当我们订阅消息时会创建Subscription的实例并将其添加到字典中。根据您的选择它可能是强引用或者弱引用。然后它将创建一个SubscriptionToken这是一个实现IDisposable接口来管理订阅的类 public sealed class SubscriptionToken : IDisposable { public Guid Id { get; private set; } private readonly Action _disposeMe; private readonly object _dependentObject; public SubscriptionToken(Guid id, Action disposeMe, object dependentObject) { Id id; _disposeMe disposeMe; _dependentObject dependentObject; } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } private void Dispose(bool isDisposing) { if (isDisposing) { _disposeMe(); } } }当我们创建SubscriptionToken的实例时实际上我们传递了一个方法来销毁自己-因此当调用Dispose方法时它将首先取消订阅。4.2 Unsubscribe - 取消订阅取消订阅消息的方法如下所示 public async Task UnsubscribeTMessage(SubscriptionToken subscriptionToken) where TMessage : Message { await UnsubscribeInternalTMessage(subscriptionToken.Id); } private async Task UnsubscribeInternalTMessage(Guid subscriptionId) where TMessage : Message { if (_subscriptions.TryGetValue(typeof(TMessage), out var messageSubscriptions)) { if (messageSubscriptions.ContainsKey(subscriptionId)) { var result messageSubscriptions.TryRemove(subscriptionId, out BaseSubscription value); } } }这段代码很容易理解。当我们取消订阅消息时订阅将从字典中删除。4.3 Publish - 发布我们已经订阅了消息并创建了存储在字典中的订阅实例。现在可以发布消息了。发布消息的方法如下所示 public async Task PublishTMessage(TMessage message) where TMessage : Message { if (message null) { throw new ArgumentNullException(nameof(message)); } ListBaseSubscription toPublish null; Type messageType message.GetType(); if (_subscriptions.TryGetValue(messageType, out var messageSubscriptions)) { toPublish messageSubscriptions.Values.OrderByDescending(x x.Priority).ToList(); } if (toPublish null || toPublish.Count 0) { return; } ListGuid deadSubscriptionIds new ListGuid(); foreach (var subscription in toPublish) { // Execute the action for this message. var result await subscription.Invoke(message); if (!result) { deadSubscriptionIds.Add(subscription.Id); } } if (deadSubscriptionIds.Any()) { await PurgeDeadSubscriptions(messageType, deadSubscriptionIds); } }当我们发布一条消息时MessageHub将查询字典以检索该消息的订阅列表然后循环执行操作。需要注意的另一件事是由于某些订阅可能是弱引用因此需要检查执行结果。如果引用已经被GC收集则执行结果会返回false这时候需要将该订阅从订阅列表中删除。5. 用法5.1 从NuGet安装PM Install-Package FunCoding.CoreMessenger在整个应用程序域中将MessengerHub.Instance用作单例模式。它提供了以下方法发布public async Task PublishTMessage(TMessage message)订阅public SubscriptionToken SubscribeTMessage(ActionTMessage action, ReferenceType referenceType ReferenceType.Weak, SubscriptionPriority priority SubscriptionPriority.Normal, string tag null)取消订阅public async Task UnsubscribeTMessage(SubscriptionToken subscriptionToken)5.2 创建Message类首先定义一个从Message继承的类如下所示public class TestMessage : Message{ public string ExtraContent { get; private set; } public TestMessage(object sender, string content) : base(sender) { ExtraContent content; }}然后在组件A中创建Message的实例如下所示var message new TestMessage(this, Test Content);5.3 订阅定义一个SubscriptionToken实例来存储订阅。在组件B中订阅消息如下所示public class HomeViewModel { private readonly SubscriptionToken _subscriptionTokenForTestMessage; public HomeViewModel() { _subscriptionTokenForTestMessage MessengerHub.Instance.SubscribeTestMessage(OnTestMessageReceived, ReferenceType.Weak, SubscriptionPriority.Normal); } private void OnTestMessageReceived(TestMessage message) {#if DEBUG System.Diagnostics.Debug.WriteLine($Received messages of type {message.GetType().ToString()}. Content: {message.Content});#endif } }5.4 发布Message在组件A中发布消息public async Task PublishMessage(){ await MessengerHub.Instance.Publish(new TestMessage(this, $Hello World!));}就是这么简单。5.5 参数Subscribe方法的完整签名为public SubscriptionToken SubscribeTMessage(ActionTMessage action, ReferenceType referenceType ReferenceType.Weak, SubscriptionPriority priority SubscriptionPriority.Normal, string tag null) where TMessage : Message您可以指定以下参数- ReferenceType。默认值为 ReferenceType.Weak因此您不必担心内存泄漏。一旦SubscriptionToken实例超出范围GC便可以自动收集它但不确定何时。如果需要保留强引用请将参数指定为ReferenceType.Strong以使GC无法收集它。-SubscriptionPriority。默认值为SubscriptionPriority.Normal。有时需要控制一个“消息”的订阅的执行顺序。在这种情况下请为订阅指定不同的优先级以控制执行顺序。注意该参数不适用于不同的Message。-Tag。为订阅指定一个标签是可选的。5.6 取消订阅您可以使用以下方法取消订阅- 使用Unsubscribe方法如下所示await MessengerHub.Instance.UnsubscribeTestMessage(_subscriptionTokenForTestMessage);- 使用SubscriptionToken的Dispose方法_subscriptionTokenForTestMessage.Dispose();在许多情况下您不会直接调用这些方法。如果使用强订阅类型则可能会导致内存泄漏问题。因此建议使用ReferenceType.Weak。请注意如果令牌未存储在上下文中则GC可能会立即收集它。例如public void MayNotEverReceiveAMessage(){ var token MessengerHub.Instance.SubscribeTestMessage((message) { // Do something here }); // token goes out of scope now // - so will be garbage collected *at some point* // - so the action may never get called}6. 与MvvmCross.Messenger的差异如果您已经使用MvvmCross开发应用程序并无需在ViewModel层之外传递消息请直接使用MvvmCross.Messenger。我仅实现了一些主要方法没有提供UI线程调度的功能并删除了对MvvmCross组件的依赖因此只要您的项目目标.NET Standard 2.0以上就可以在任何WPFUWP和Xamarin项目中使用。另外Publish方法始终在后台运行以避免阻塞UI。但是您应该知道何时需要返回UI线程尤其是当您需要与UI控件进行交互时。另一个区别是无需使用DI来创建MessageHub实例该实例是所有应用程序域中的单例实例。如果解决方案包含需要相互通信的多个组件则单例模式会比较简单DI将使其更加复杂。请点击阅读原文查看GitHub链接。如果觉得有用欢迎加星????了解新西兰IT行业真实码农生活请长按上方二维码关注“程序员在新西兰”