🧑 博主简介:CSDN博客专家、CSDN平台优质创作者,高级开发工程师,数学专业,10年以上C/C++, C#, Java等多种编程语言开发经验,拥有高级工程师证书;擅长C/C++、C#等开发语言,熟悉Java常用开发技术,能熟练应用常用数据库SQL server,Oracle,mysql,postgresql等进行开发应用,熟悉DICOM医学影像及DICOM协议,业余时间自学JavaScript,Vue,qt,python等,具备多种混合语言开发能力。撰写博客分享知识,致力于帮助编程爱好者共同进步。欢迎关注、交流及合作,提供技术支持与解决方案。
技术合作请加本人wx(注明来自csdn):xt20160813
详解 C# 中基于发布-订阅模式的 Messenger 消息传递机制:Messenger.Default.Send/Register
在 C# 开发中,特别是在 MVVM(Model-View-ViewModel)架构的客户端应用程序中,跨组件通信是实现模块化设计和松耦合的关键。Messenger
是一种基于发布-订阅模式(Publish-Subscribe Pattern)的消息传递机制,广泛应用于 WPF、WinForms 和其他 C# 框架中。本文以 Messenger.Default.Send
和 Messenger.Default.Register
为核心,结合历史对话中提到的代码示例(如 Messenger.Default.Send<string>("Recovery", "fanxuan")
),详细解析其实现原理、应用场景、技术细节,并提供完整代码示例和开发注意事项。文章特别融入国产化环境(如银河麒麟系统)的开发视角,确保内容准确、实用。
一、引言
发布-订阅模式是一种设计模式,允许消息发布者(Publisher)将消息发送给订阅者(Subscriber),而无需直接耦合。Messenger
是 MVVM 框架(如 MVVM Light、Prism 或自定义实现)中常见的消息传递工具,通过 Messenger.Default.Send
和 Messenger.Default.Register
实现异步、定向的模块间通信。
在历史对话中,代码片段展示了通过 Messenger.Default.Send<string>("Recovery", "fanxuan")
发送消息,并在接收端通过 Messenger.Default.Register<string>
捕获消息并触发 button1_Click
方法。这种机制广泛用于 UI 状态管理(如恢复按钮状态)、跨 ViewModel 通信或后台服务协调。本文将从原理、实现、代码示例到国产化开发适配,全面解析这一机制。
二、发布-订阅模式与 Messenger 概述
1. 发布-订阅模式
发布-订阅模式的核心思想是:
- 发布者:发送消息但不关心谁接收。
- 订阅者:注册对特定消息的兴趣,接收并处理消息。
- 消息中介:管理消息的分发,通常通过令牌(Token)或消息类型过滤。
优点:
- 松耦合:发布者和订阅者无需直接引用,降低模块间依赖。
- 灵活性:支持一对多通信,动态注册/注销订阅者。
- 线程安全:常结合 Dispatcher 机制,确保 UI 操作在主线程执行。
2. Messenger 的功能
Messenger
是 MVVM 框架中的消息传递工具(如 MVVM Light 的 GalaSoft.MvvmLight.Messaging.Messenger
),提供以下功能:
- 发送消息:通过
Messenger.Default.Send<T>(message, token)
发送特定类型的消息。 - 注册订阅:通过
Messenger.Default.Register<T>(recipient, token, action)
注册消息处理逻辑。 - 令牌机制:使用
token
(如字符串"fanxuan"
)实现定向消息传递,减少无关模块的处理开销。 - 线程调度:结合
Dispatcher
或异步机制,确保线程安全。
在历史对话中,Messenger.Default.Send<string>("Recovery", "fanxuan")
表示发送字符串消息 "Recovery"
,目标是注册了 "fanxuan"
令牌的订阅者。
三、Messenger 的核心方法
1. Messenger.Default.Send
签名:
void Send<T>(T message, object token = null);
- 参数:
T message
:消息内容,支持任意类型(如string
、自定义类)。token
:可选的令牌,用于过滤接收者,通常为字符串或对象。
- 作用:将消息广播给所有匹配
token
和消息类型T
的订阅者。 - 示例:
// 发送字符串消息 "Recovery",令牌为 "fanxuan" Messenger.Default.Send<string>("Recovery", "fanxuan");
2. Messenger.Default.Register
签名:
void Register<T>(object recipient, object token, Action<T> action);
- 参数:
recipient
:订阅者对象(通常为this
,表示当前类实例)。token
:与发送端匹配的令牌。action
:收到消息后执行的回调函数,接收消息内容T
。
- 作用:注册一个消息处理逻辑,仅处理匹配
token
和类型T
的消息。 - 示例:
// 注册处理 "fanxuan" 令牌的字符串消息 Messenger.Default.Register<string>( this, "fanxuan", message => { Console.WriteLine($"Received: {message}"); } );
3. 消息传递流程
以下是 Messenger
的工作流程(结合历史对话中的代码):
graph LR
A[Send: Messenger.Default.Send<string>("Recovery", "fanxuan")] --> B[Messenger 中介]
B --> C[查找匹配 "fanxuan" 和 string 的订阅者]
C --> D[Register: Messenger.Default.Register<string>(this, "fanxuan", ...)]
D --> E[执行回调: DicomOperateDispatcher.Invoke(button1_Click)]
四、Messenger 的实现原理
Messenger
通常基于单例模式(Messenger.Default
),内部维护一个订阅者列表。核心实现逻辑如下:
消息注册:
Register
方法将订阅者的{recipient, token, action}
组合存储在一个字典或列表中。- 键为消息类型
T
和token
,值为回调函数Action<T>
。
消息发送:
Send
方法遍历订阅者列表,查找匹配T
和token
的注册项。- 对每个匹配的订阅者,调用其
action
回调,传递消息内容。
线程安全:
Messenger
通常不直接处理线程切换,需结合Dispatcher
(如 WPF 的Dispatcher.Invoke
)确保 UI 操作在主线程执行。
令牌机制:
- 令牌(
token
)通过哈希比较(如字符串的Equals
方法)实现高效匹配。 - 如果
token
为null
,消息广播给所有订阅了类型T
的接收者。
- 令牌(
五、Messenger 在历史对话中的应用
在历史对话中,代码展示了 Messenger
在医学影像系统(DICOM 相关)中的应用,用于触发恢复按钮状态:
发送端:
case IFCCallBackMSG.Recovery: // 复原按钮状态通过 ESC 键触发
{
// 发送恢复命令
Messenger.Default.Send<string>("Recovery", "fanxuan");
break;
}
- 场景:当用户按下 ESC 键,系统触发
IFCCallBackMSG.Recovery
状态,发送"Recovery"
消息,目标为"fanxuan"
通道。 - 目的:通知订阅了
"fanxuan"
的模块执行状态恢复(如恢复 UI 按钮或 DICOM 数据状态)。
接收端:
Messenger.Default.Register<string>(
this,
"fanxuan",
message => DicomOperateDispatcher.Invoke(new Onclick(button1_Click), null, null)
);
- 场景:某个类(可能是 View 或 ViewModel)注册了
"fanxuan"
通道的字符串消息处理。 - 处理逻辑:收到消息后,通过
DicomOperateDispatcher.Invoke
调用button1_Click
方法,可能是恢复 UI 按钮状态或重置 DICOM 数据。 - 线程安全:
DicomOperateDispatcher.Invoke
确保button1_Click
在 UI 线程执行,避免跨线程访问冲突。
分析:
- 消息内容:
"Recovery"
表示恢复命令,可能触发 UI 重置、数据恢复或其他状态复原。 - 令牌:
"fanxuan"
(可能意为“反选”或特定模块标识)确保消息只发送给特定订阅者。 - 问题:当前代码未对
message
内容进行判断,可能响应所有"fanxuan"
通道的消息,建议添加条件判断(如if (message == "Recovery")
)。
六、完整代码示例
以下是一个完整的 C# 示例,展示 Messenger
的发送和接收过程,结合 WPF 和 MVVM 架构,模拟恢复按钮状态的场景。
1. 安装 MVVM Light
在项目中添加 MVVM Light NuGet 包:
Install-Package MvvmLight
2. 发送端(MainViewModel.cs)
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Messaging;
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
// 模拟 ESC 键触发恢复
SendRecoveryCommand();
}
private void SendRecoveryCommand()
{
// 发送恢复消息,目标为 "fanxuan" 通道
Messenger.Default.Send<string>("Recovery", "fanxuan");
}
}
3. 接收端(ButtonViewModel.cs)
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Messaging;
using System.Windows;
public class ButtonViewModel : ViewModelBase
{
public ButtonViewModel()
{
// 注册 "fanxuan" 通道的字符串消息
Messenger.Default.Register<string>(
this,
"fanxuan",
message =>
{
// 仅处理 "Recovery" 消息
if (message == "Recovery")
{
// 在 UI 线程执行按钮状态恢复
Application.Current.Dispatcher.Invoke(() =>
{
ButtonClickHandler();
});
}
});
}
private void ButtonClickHandler()
{
// 模拟恢复按钮状态
MessageBox.Show("Button state recovered!");
// 实际逻辑:重置 UI 按钮、DICOM 数据状态等
}
}
4. 主程序(App.xaml.cs)
using GalaSoft.MvvmLight.Messaging;
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
// 初始化 ViewModel
var mainViewModel = new MainViewModel();
var buttonViewModel = new ButtonViewModel();
}
}
运行结果:
- 当
MainViewModel
发送"Recovery"
消息时,ButtonViewModel
捕获消息并执行ButtonClickHandler
,显示提示框。 Dispatcher.Invoke
确保 UI 操作在主线程执行。
七、国产化环境中的应用
结合历史对话中关于银河麒麟系统的离线部署经验,以下是 Messenger
在国产化环境中的开发注意事项:
离线安装 MVVM Light:
- 在银河麒麟上离线安装 NuGet 包:
sudo rpm -ivh mvvmlightlibs-5.4.1.rpm --nodeps --force
- 确保项目引用
GalaSoft.MvvmLight.dll
。
- 在银河麒麟上离线安装 NuGet 包:
DICOM 集成:
Messenger
可用于 DICOM 系统中模块间通信(如 UI 与 PACS 数据同步)。- 示例:解析 DICOM 文件并发送状态更新:
using pydicom; // 需通过 Python 脚本解析后传递 var ds = pydicom.dcmread("mri.dcm"); Messenger.Default.Send<string>("DICOMLoaded", "fanxuan");
防火墙配置:
- 如果
Messenger
用于跨进程通信(如与 Orthanc PACS 服务交互),需放行端口:sudo firewall-cmd --zone=public --add-port=4242/tcp --permanent sudo firewall-cmd --reload
- 如果
Fitten Code 集成:
- 使用 Fitten Code 生成消息处理代码:
// @Fitten 生成 Messenger 注册代码 Messenger.Default.Register<string>( this, "fanxuan", message => { if (message == "Recovery") { /* 恢复逻辑 */ } } );
- 使用 Fitten Code 生成消息处理代码:
八、常见问题与解决方案
问题 | 解决方案 |
---|---|
消息未被接收 | 检查 Register 和 Send 的 token 和消息类型是否匹配。 |
UI 线程冲突 | 使用 Dispatcher.Invoke 确保回调在主线程执行。 |
国产系统依赖缺失 | 离线安装 MVVM Light:sudo rpm -ivh mvvmlightlibs.rpm --nodeps 。 |
消息广播过广 | 添加 token (如 "fanxuan" )或条件判断(如 if (message == "Recovery") )。 |
九、总结
Messenger.Default.Send
和 Messenger.Default.Register
是 C# 中基于发布-订阅模式的强大消息传递机制,适用于 MVVM 架构中的模块间通信。核心特点:
- 松耦合:通过
token
实现定向消息传递。 - 线程安全:结合
Dispatcher
确保 UI 操作安全。 - 灵活性:支持任意消息类型和动态注册/注销。
结合历史对话中的 DICOM 系统场景,Messenger
在恢复按钮状态、跨模块通信中发挥了关键作用。在国产化环境(如银河麒麟)中,需注意离线依赖管理和防火墙配置。提供的代码示例可直接运行,适合开发者快速上手。
建议:
- 在接收端添加消息内容判断,提升代码健壮性。
- 使用 MVVM Light 或 Prism 的
Messenger
实现标准化开发。 - 在国产化环境中测试消息传递的性能和稳定性。
参考资料
- MVVM Light 文档:http://www.mvvmlight.net/
- DICOM 标准:http://dicom.nema.org/
- C# 委托与事件:https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/events/