WPF MVVM Messenger
大约 15 分钟约 4368 字
WPF MVVM Messenger
简介
MVVM Messenger(消息总线)是 ViewModel 之间松耦合通信的核心模式。通过发布/订阅消息,ViewModel 不需要互相引用即可传递数据和事件通知。CommunityToolkit.Mvvm 提供了 WeakReferenceMessenger 和 StrongReferenceMessenger 两种实现。在工业上位机和复杂桌面应用中,Messenger 模式能够有效解耦各业务模块,使代码更易于维护和扩展。
消息总线的本质
消息总线的核心思想来源于观察者模式(Observer Pattern)和发布/订阅模式(Pub/Sub)。在传统的事件驱动编程中,事件发送方需要持有事件接收方的引用,这导致了强耦合。消息总线通过引入一个中间层(Message Bus)来解耦发送方和接收方:
发送方 → [消息总线] → 接收方 A
→ 接收方 B
→ 接收方 C在 WPF MVVM 架构中,ViewModel 之间通常不应该互相持有引用。例如,DeviceViewModel(设备管理)需要通知 AlarmViewModel(告警管理)某个设备离线了,但它不应该直接引用 AlarmViewModel。Messenger 模式解决了这个问题。
CommunityToolkit.Mvvm 的两种实现
| 特性 | WeakReferenceMessenger | StrongReferenceMessenger |
|---|---|---|
| 引用方式 | 弱引用(WeakReference) | 强引用 |
| 内存管理 | 自动回收 | 需手动取消订阅 |
| 性能 | 稍低(弱引用检查) | 更高 |
| 适用场景 | UI 层(ViewModel 生命周期不确定) | 高频消息、短生命周期对象 |
| 线程安全 | 是 | 是 |
特点
实现
CommunityToolkit.Mvvm Messenger
基础消息定义
消息类型是 Messenger 模式的核心。推荐使用 record 类型定义消息,因为 record 天然具备不可变性和值语义:
using CommunityToolkit.Mvvm.Messaging;
// ========== 消息定义 ==========
// 设备状态变更消息
public record DeviceStatusMessage(string DeviceId, bool IsOnline, string? Description = null);
// 告警消息
public record AlarmMessage(
string AlarmId,
string Description,
AlarmLevel Level,
DateTime Timestamp = default);
public enum AlarmLevel { Info, Warning, Critical }
// 导航消息 — 用于页面切换
public record NavigationMessage(string ViewName, object? Parameter = null);
// 数据刷新消息 — 通知其他模块刷新数据
public record DataRefreshMessage(string DataType, string? Filter = null);
// 用户操作消息 — 记录用户关键操作
public record UserActionMessage(string Action, string Target, DateTime Timestamp = default);发送消息
// ========== 发送消息 ==========
public class DeviceViewModel : ObservableObject
{
private readonly IDeviceService _deviceService;
public DeviceViewModel(IDeviceService deviceService)
{
_deviceService = deviceService;
}
// 同步发送 — 设备连接状态变更
public async Task ConnectDeviceAsync(string deviceId)
{
try
{
await _deviceService.ConnectAsync(deviceId);
// 发送设备上线消息
WeakReferenceMessenger.Default.Send(
new DeviceStatusMessage(deviceId, IsOnline: true, "设备连接成功"));
// 发送用户操作记录
WeakReferenceMessenger.Default.Send(
new UserActionMessage("ConnectDevice", deviceId));
}
catch (Exception ex)
{
// 发送设备离线告警
WeakReferenceMessenger.Default.Send(
new AlarmMessage(
AlarmId: Guid.NewGuid().ToString(),
Description: $"设备 {deviceId} 连接失败: {ex.Message}",
Level: AlarmLevel.Critical,
Timestamp: DateTime.Now));
}
}
// 批量发送 — 通知多个模块数据已刷新
public void OnDataUpdated()
{
WeakReferenceMessenger.Default.Send(new DataRefreshMessage("DeviceList"));
WeakReferenceMessenger.Default.Send(new DataRefreshMessage("Dashboard"));
}
}IRecipient 接口订阅
// ========== 使用 IRecipient<T> 接口订阅 ==========
public class DashboardViewModel : ObservableObject,
IRecipient<DeviceStatusMessage>,
IRecipient<AlarmMessage>,
IRecipient<DataRefreshMessage>,
IDisposable
{
private readonly ObservableCollection<AlarmItem> _alarms = new();
public ObservableCollection<AlarmItem> Alarms => _alarms;
private readonly ObservableCollection<DeviceStatusItem> _devices = new();
public ObservableCollection<DeviceStatusItem> Devices => _devices;
public DashboardViewModel()
{
// 注册所有需要处理的消息类型
WeakReferenceMessenger.Default.Register<DeviceStatusMessage>(this);
WeakReferenceMessenger.Default.Register<AlarmMessage>(this);
WeakReferenceMessenger.Default.Register<DataRefreshMessage>(this);
}
// 处理设备状态消息
public void Receive(DeviceStatusMessage message)
{
Application.Current.Dispatcher.Invoke(() =>
{
var existing = _devices.FirstOrDefault(d => d.DeviceId == message.DeviceId);
if (existing != null)
{
existing.IsOnline = message.IsOnline;
existing.LastUpdate = DateTime.Now;
}
else
{
_devices.Add(new DeviceStatusItem(
message.DeviceId, message.IsOnline, DateTime.Now));
}
});
}
// 处理告警消息
public void Receive(AlarmMessage message)
{
Application.Current.Dispatcher.Invoke(() =>
{
_alarms.Add(new AlarmItem(
message.AlarmId, message.Description, message.Level, message.Timestamp));
// 限制告警列表数量,防止内存增长
while (_alarms.Count > 500)
_alarms.RemoveAt(0);
// Critical 级别告警弹出通知
if (message.Level == AlarmLevel.Critical)
{
WeakReferenceMessenger.Default.Send(
new UserActionMessage("CriticalAlarm", message.AlarmId));
}
});
}
// 处理数据刷新消息
public void Receive(DataRefreshMessage message)
{
// 根据消息类型决定刷新哪些数据
Application.Current.Dispatcher.Invoke(() =>
{
// 触发属性变更通知,更新绑定
OnPropertyChanged(nameof(Alarms));
OnPropertyChanged(nameof(Devices));
});
}
public void Dispose()
{
// 重要:取消所有订阅,防止内存泄漏
WeakReferenceMessenger.Default.UnregisterAll(this);
}
}
// 辅助记录类型
public record DeviceStatusItem(string DeviceId, bool IsOnline, DateTime LastUpdate);
public record AlarmItem(string AlarmId, string Description, AlarmLevel Level, DateTime Timestamp);使用 Lambda 注册消息
// ========== 使用 Lambda 注册(无需实现 IRecipient 接口)==========
public class SettingsViewModel : ObservableObject, IDisposable
{
public SettingsViewModel()
{
// Lambda 方式注册 — 更灵活,适合简单处理
WeakReferenceMessenger.Default.Register<NavigationMessage>(this, (r, msg) =>
{
if (r is SettingsViewModel vm && msg.ViewName == "Settings")
{
vm.LoadSettings(msg.Parameter?.ToString());
}
});
// 带消息令牌的注册 — 限定消息范围
WeakReferenceMessenger.Default.Register<DeviceStatusMessage, string>(
this, "SettingsChannel", (r, msg) =>
{
// 只接收来自 SettingsChannel 的设备状态消息
if (r is SettingsViewModel vm)
vm.UpdateDeviceInSettings(msg.DeviceId, msg.IsOnline);
});
}
private void LoadSettings(string? section) { /* 加载设置 */ }
private void UpdateDeviceInSettings(string deviceId, bool isOnline) { /* 更新设备设置 */ }
public void Dispose()
{
WeakReferenceMessenger.Default.UnregisterAll(this);
}
}自定义 Messenger 实现
线程安全的消息总线
// ========== 自定义消息总线实现 ==========
/// <summary>
/// 线程安全的简单消息总线
/// 适用于不使用 CommunityToolkit.Mvvm 的项目
/// </summary>
public static class SimpleMessenger
{
// 消息处理器字典:消息类型 → 处理器列表
private static readonly ConcurrentDictionary<Type, List<WeakAction>> _handlers = new();
private static readonly object _lock = new();
/// <summary>
/// 注册消息处理器
/// </summary>
public static void Register<TMessage>(object recipient, Action<TMessage> handler)
{
var handlers = _handlers.GetOrAdd(typeof(TMessage), _ => new List<WeakAction>());
lock (_lock)
{
// 清理已失效的弱引用
handlers.RemoveAll(h => !h.IsAlive);
handlers.Add(new WeakAction(recipient, handler));
}
}
/// <summary>
/// 取消注册
/// </summary>
public static void Unregister<TMessage>(object recipient)
{
if (_handlers.TryGetValue(typeof(TMessage), out var handlers))
{
lock (_lock)
{
handlers.RemoveAll(h =>
!h.IsAlive || h.Recipient == recipient);
}
}
}
/// <summary>
/// 发送消息
/// </summary>
public static void Send<TMessage>(TMessage message)
{
if (!_handlers.TryGetValue(typeof(TMessage), out var handlers))
return;
// 创建快照,防止遍历期间修改
WeakAction[] snapshot;
lock (_lock)
{
snapshot = handlers.Where(h => h.IsAlive).ToArray();
}
foreach (var handler in snapshot)
{
handler.Invoke(message);
}
}
/// <summary>
/// 清理所有已失效的处理器
/// </summary>
public static void Cleanup()
{
foreach (var kvp in _handlers)
{
lock (_lock)
{
kvp.Value.RemoveAll(h => !h.IsAlive);
}
}
}
/// <summary>
/// 弱引用包装,防止订阅方无法被 GC 回收
/// </summary>
private class WeakAction
{
private readonly WeakReference _recipientRef;
private readonly Delegate _handler;
public WeakAction(object recipient, Delegate handler)
{
_recipientRef = new WeakReference(recipient);
_handler = handler;
}
public object? Recipient => _recipientRef.Target;
public bool IsAlive => _recipientRef.IsAlive;
public void Invoke<TMessage>(TMessage message)
{
if (_recipientRef.Target is not null)
(_handler as Action<TMessage>)?.Invoke(message);
}
}
}
// 使用示例
public class DeviceViewModel : ObservableObject
{
public void OnDeviceConnected(string deviceId)
{
SimpleMessenger.Send(new DeviceStatusMessage(deviceId, true));
}
}
public class AlarmViewModel : ObservableObject, IDisposable
{
public AlarmViewModel()
{
SimpleMessenger.Register<DeviceStatusMessage>(this, msg =>
{
Console.WriteLine($"设备 {msg.DeviceId} 状态: {(msg.IsOnline ? "在线" : "离线")}");
});
}
public void Dispose()
{
SimpleMessenger.Unregister<DeviceStatusMessage>(this);
}
}带令牌的定向消息
消息令牌与频道
// ========== 消息令牌(Token)机制 ==========
// 使用 Token 区分同一消息类型的不同频道
// 场景:多个 Tab 页面各自接收属于自己的数据更新通知
public class TabViewModel : ObservableObject, IDisposable
{
private readonly string _tabId;
public TabViewModel(string tabId)
{
_tabId = tabId;
// 注册时指定 Token,只接收该 Token 的消息
WeakReferenceMessenger.Default.Register<NavigationMessage, string>(
this, _tabId, (r, msg) =>
{
if (r is TabViewModel vm)
{
vm.HandleNavigation(msg);
}
});
}
// 发送定向消息到特定 Tab
public void SendToTab(string targetTabId, string viewName)
{
WeakReferenceMessenger.Default.Send(
new NavigationMessage(viewName), targetTabId);
}
// 广播消息到所有 Tab
public void Broadcast(string viewName)
{
WeakReferenceMessenger.Default.Send(new NavigationMessage(viewName));
}
private void HandleNavigation(NavigationMessage message) { /* 处理导航 */ }
public void Dispose()
{
WeakReferenceMessenger.Default.Unregister<NavigationMessage, string>(this, _tabId);
}
}ValueChangedMessage 传递简单值
// ========== ValueChangedMessage — 传递简单值类型 ==========
// CommunityToolkit.Mvvm 内置的 ValueChangedMessage<T> 用于传递值变更
// 适用于简单的状态通知场景
public class ThemeViewModel : ObservableObject
{
public ThemeViewModel()
{
// 注册值变更消息
WeakReferenceMessenger.Default.Register<ValueChangedMessage<string>>(this, (r, msg) =>
{
// msg.Value 获取变更后的值
Console.WriteLine($"主题变更为: {msg.Value}");
});
}
// 发送值变更
public void ChangeTheme(string newTheme)
{
WeakReferenceMessenger.Default.Send(new ValueChangedMessage<string>(newTheme));
}
}
// 自定义值变更消息(更语义化)
public record ThemeChangedMessage(string ThemeName, bool IsDarkMode);
public class MainWindowViewModel : ObservableObject, IRecipient<ThemeChangedMessage>
{
public MainWindowViewModel()
{
WeakReferenceMessenger.Default.Register<ThemeChangedMessage>(this);
}
public void Receive(ThemeChangedMessage message)
{
// 应用主题
CurrentTheme = message.ThemeName;
IsDarkMode = message.IsDarkMode;
}
public string CurrentTheme { get; private set; } = "Light";
public bool IsDarkMode { get; private set; }
}异步消息与请求-响应模式
// ========== 异步请求-响应消息 ==========
// CommunityToolkit.Mvvm 支持异步消息处理
// 适用于需要跨 ViewModel 请求数据的场景
// 定义异步请求消息
public record DeviceDataRequestMessage(string DeviceId) :
AsyncRequestMessage<IEnumerable<DeviceDataPoint>>
{
// AsyncRequestMessage<T> 内置了 Reply(T response) 方法
}
// 请求方 ViewModel
public class ChartViewModel : ObservableObject
{
public async Task LoadDeviceDataAsync(string deviceId)
{
// 发送请求消息并等待响应
var request = new DeviceDataRequestMessage(deviceId);
WeakReferenceMessenger.Default.Send(request);
// 等待响应(异步等待,不阻塞 UI 线程)
var data = await request.Response;
if (data != null)
{
// 使用响应数据更新图表
UpdateChart(data);
}
}
private void UpdateChart(IEnumerable<DeviceDataPoint> data) { /* 更新图表 */ }
}
// 响应方 ViewModel
public class DeviceViewModel : ObservableObject,
IRecipient<DeviceDataRequestMessage>
{
private readonly IDeviceDataService _dataService;
public DeviceViewModel(IDeviceDataService dataService)
{
_dataService = dataService;
WeakReferenceMessenger.Default.Register<DeviceDataRequestMessage>(this);
}
public async void Receive(DeviceDataRequestMessage message)
{
// 异步获取数据后回复
try
{
var data = await _dataService.GetDataAsync(message.DeviceId);
message.Reply(data);
}
catch (Exception ex)
{
// 回复空数据表示获取失败
message.Reply(Enumerable.Empty<DeviceDataPoint>());
}
}
}
public record DeviceDataPoint(DateTime Timestamp, double Value);消息聚合与转发
// ========== 消息聚合与转发服务 ==========
/// <summary>
/// 消息聚合服务 — 将多个底层消息聚合成高层业务消息
/// 减少下游 ViewModel 需要订阅的消息数量
/// </summary>
public class MessageAggregationService : IDisposable
{
private readonly Timer _debounceTimer;
private readonly List<DeviceStatusMessage> _pendingStatusMessages = new();
public MessageAggregationService()
{
// 订阅底层设备状态消息
WeakReferenceMessenger.Default.Register<DeviceStatusMessage>(this, OnDeviceStatus);
// 防抖定时器 — 将短时间内的多个消息合并
_debounceTimer = new Timer(OnDebounceElapsed, null,
TimeSpan.FromMilliseconds(500), TimeSpan.Infinite);
}
private void OnDeviceStatus(object recipient, DeviceStatusMessage message)
{
_pendingStatusMessages.Add(message);
// 重置防抖计时器
_debounceTimer.Change(TimeSpan.FromMilliseconds(500), TimeSpan.Infinite);
}
private void OnDebounceElapsed(object? state)
{
if (_pendingStatusMessages.Count == 0) return;
// 聚合多条消息为一条批量消息
var batch = _pendingStatusMessages.ToList();
_pendingStatusMessages.Clear();
var onlineDevices = batch.Where(m => m.IsOnline).Select(m => m.DeviceId).ToList();
var offlineDevices = batch.Where(m => !m.IsOnline).Select(m => m.DeviceId).ToList();
// 发送聚合后的消息
WeakReferenceMessenger.Default.Send(new DeviceBatchStatusMessage(
OnlineDeviceIds: onlineDevices,
OfflineDeviceIds: offlineDevices,
Timestamp: DateTime.Now));
}
public void Dispose()
{
_debounceTimer.Dispose();
WeakReferenceMessenger.Default.UnregisterAll(this);
}
}
// 批量状态消息
public record DeviceBatchStatusMessage(
IReadOnlyList<string> OnlineDeviceIds,
IReadOnlyList<string> OfflineDeviceIds,
DateTime Timestamp);ViewModel 基类集成
// ========== 在 ViewModel 基类中自动管理 Messenger 订阅 ==========
/// <summary>
/// 集成 Messenger 自动管理的 ViewModel 基类
/// 所有继承此基类的 ViewModel 在销毁时自动取消订阅
/// </summary>
public abstract class MessengerAwareViewModel : ObservableObject, IDisposable
{
private bool _isDisposed;
protected MessengerAwareViewModel()
{
// 子类可在构造函数中调用 RegisterMessages() 注册消息
RegisterMessages();
}
/// <summary>
/// 子类重写此方法注册消息处理器
/// </summary>
protected virtual void RegisterMessages() { }
/// <summary>
/// 安全注册消息(IRecipient 方式)
/// </summary>
protected void Register<TMessage>() where TMessage : class
{
WeakReferenceMessenger.Default.Register<TMessage>(this);
}
/// <summary>
/// 安全注册消息(Lambda 方式)
/// </summary>
protected void Register<TMessage>(Action<TMessage> handler) where TMessage : class
{
WeakReferenceMessenger.Default.Register<TMessage>(this, (r, msg) => handler(msg));
}
/// <summary>
/// 安全注册带 Token 的消息
/// </summary>
protected void Register<TMessage, TToken>(TToken token, Action<TMessage> handler)
where TMessage : class where TToken : notnull
{
WeakReferenceMessenger.Default.Register(this, token, (r, msg) => handler(msg));
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (_isDisposed) return;
_isDisposed = true;
if (disposing)
{
// 自动取消所有订阅
WeakReferenceMessenger.Default.UnregisterAll(this);
}
}
}
// 使用示例
public class ProductionViewModel : MessengerAwareViewModel
{
public ProductionViewModel()
{
}
protected override void RegisterMessages()
{
Register<DeviceStatusMessage>(OnDeviceStatus);
Register<AlarmMessage>(OnAlarm);
}
private void OnDeviceStatus(DeviceStatusMessage message)
{
// 处理设备状态变更
}
private void OnAlarm(AlarmMessage message)
{
// 处理告警
}
}消息调试与追踪
// ========== 消息调试追踪器 ==========
/// <summary>
/// 消息追踪器 — 开发环境记录所有消息的发送和接收
/// 帮助调试消息流向问题
/// </summary>
public class MessengerDebugger
{
private readonly Action<string> _logAction;
private readonly ConcurrentQueue<MessageLog> _logs = new();
public MessengerDebugger(Action<string>? logAction = null)
{
_logAction = logAction ?? (msg => Debug.WriteLine(msg));
}
public void LogSend<TMessage>(TMessage message, object? sender = null)
{
var entry = new MessageLog(
Direction: "Send",
MessageType: typeof(TMessage).Name,
Sender: sender?.GetType().Name ?? "Unknown",
Timestamp: DateTime.Now,
Content: message?.ToString() ?? "");
_logs.Enqueue(entry);
_logAction($"[Messenger] Send {typeof(TMessage).Name} from {entry.Sender}");
}
public void LogReceive<TMessage>(object receiver)
{
var entry = new MessageLog(
Direction: "Receive",
MessageType: typeof(TMessage).Name,
Sender: receiver.GetType().Name,
Timestamp: DateTime.Now,
Content: "");
_logs.Enqueue(entry);
_logAction($"[Messenger] Receive {typeof(TMessage).Name} at {entry.Sender}");
}
public IEnumerable<MessageLog> GetRecentLogs(int count = 100) =>
_logs.TakeLast(count);
public void Clear() => _logs.Clear();
}
public record MessageLog(
string Direction, string MessageType, string Sender,
DateTime Timestamp, string Content);
// 使用方式:包装 WeakReferenceMessenger
public static class TracedMessenger
{
private static readonly MessengerDebugger _debugger = new();
public static void Send<TMessage>(TMessage message) where TMessage : class
{
_debugger.LogSend(message);
WeakReferenceMessenger.Default.Send(message);
}
}MVVM 最佳实践
消息命名规范
项目消息命名规范:
1. 状态变更:{Entity}{Status}Message
- DeviceStatusMessage, ConnectionStatusMessage
2. 操作通知:{Action}{Target}Message
- DeviceConnectedMessage, OrderCreatedMessage
3. 数据请求:{Entity}{Request/Response}Message
- DeviceDataRequestMessage
4. 导航事件:{Navigation}Message
- NavigationMessage, TabSwitchMessage
5. 错误告警:{Error/Alarm}Message
- AlarmMessage, ConnectionErrorMessage何时使用 Messenger,何时使用共享服务
// ========== 场景对比 ==========
// 场景 1:一对多通知 → 使用 Messenger
// 多个 ViewModel 需要响应同一个事件
// 例如:设备上线 → Dashboard 更新状态 + 告警模块记录 + 日志模块记录
WeakReferenceMessenger.Default.Send(new DeviceStatusMessage("PLC-001", true));
// 场景 2:请求-响应 → 使用共享服务
// ViewModel 需要主动获取数据,而不是被动等待通知
public class OrderViewModel
{
private readonly IOrderService _orderService; // 共享服务
// 比 Messenger 的 AsyncRequestMessage 更直观
}
// 场景 3:频繁双向通信 → 使用共享服务
// 例如:设备轮询服务持续推送数据到多个 ViewModel
public class DevicePollingService : ObservableObject
{
// 通过 INotifyPropertyChanged 和 ObservableCollection 通知
public ObservableCollection<DeviceData> LatestData { get; } = new();
}
// 场景 4:跨模块松耦合通知 → 使用 Messenger
// 模块 A 完成操作后通知模块 B,但不需要模块 B 的响应
WeakReferenceMessenger.Default.Send(new DataRefreshMessage("Orders"));消息与事件的选择指南
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 父 ViewModel 通知子 ViewModel | 直接引用或事件 | 层级明确,不需要中间层 |
| 同级 ViewModel 通信 | Messenger | 无直接引用关系 |
| 全局通知(主题切换、用户登出) | Messenger | 广播性质 |
| 请求数据 | 共享服务接口 | 有明确的返回值需求 |
| 设备数据推送 | ObservableObject / ObservableCollection | 持续数据流 |
| 一次性操作通知 | Messenger | 事件性质 |
优点
缺点
总结
Messenger 模式通过发布/订阅实现 ViewModel 之间的松耦合通信。CommunityToolkit.Mvvm 的 WeakReferenceMessenger 使用弱引用自动管理订阅者生命周期。建议为每种业务事件定义明确的 record 消息类型,及时取消订阅避免内存泄漏。在 ViewModel 基类中集成自动注册/取消注册机制可以减少样板代码。
关键知识点
- WeakReferenceMessenger 使用弱引用,订阅者被 GC 后自动失效,无需手动取消订阅。
- StrongReferenceMessenger 使用强引用,性能更好但必须手动取消订阅。
- 消息类型应该是 record 类型,保证不可变性和值语义。
- Send 是同步的,跨线程需手动 Dispatcher.Invoke 或使用异步消息。
IRecipient<T>接口比 Lambda 注册更清晰,适合需要处理多种消息的 ViewModel。AsyncRequestMessage<T>支持请求-响应模式,适合跨 ViewModel 的数据请求。
项目落地视角
- 为每种业务事件定义独立的 message record 类型,放在 Messages 文件夹统一管理。
- 在 ViewModel 基类中自动处理 Register/Unregister,避免遗漏。
- 避免用 Messenger 传递大量数据,优先使用共享服务传递大数据集。
- 在开发环境启用消息追踪器,记录消息发送和接收日志。
- 为消息定义统一的命名规范,便于团队协作。
常见误区
- 使用 Messenger 代替所有 ViewModel 间通信,导致代码流向不可追踪。
- 忘记 Unregister 导致已关闭的 ViewModel 仍然接收消息(内存泄漏)。
- 在 Receive 中执行耗时操作阻塞 UI 线程,应使用 Task.Run 或异步处理。
- 消息类型定义过于宽泛(如 object),失去类型安全的好处。
- 发送消息后立即期望接收方已完成处理(Send 是同步的,但异步处理不保证顺序)。
进阶路线
- 学习 EventAggregator(Prism)和 MediatR 的消息模式,了解不同的实现哲学。
- 研究响应式编程(Rx.NET / System.Reactive)的事件流处理,适用于高频数据流。
- 了解进程间通信(IPC)的消息传递模式,如 Named Pipes、gRPC。
- 实现消息持久化和重放机制,用于调试和问题还原。
适用场景
- 多个 ViewModel 需要响应同一事件(如设备上线通知、数据刷新)。
- 子窗口需要通知主窗口状态变更(如设置保存后刷新配置)。
- 跨模块的松耦合通信(如告警模块通知 Dashboard 更新)。
- 全局事件广播(如主题切换、用户登出、语言切换)。
落地建议
- 建立项目消息类型目录(如 /Messages/),统一管理所有消息定义。
- 在 ViewModel 基类中自动处理 Register/Unregister。
- 为消息处理添加日志,方便调试追踪。
- 编写单元测试验证消息的发送和接收逻辑。
- 定期检查 MessengerDebugger 日志,发现异常的消息流向。
排错清单
- 检查消息类型是否匹配(泛型参数,注意 record 的值相等语义)。
- 确认 Register 和 Send 使用同一个 Messenger 实例(默认都是 Default)。
- 检查订阅者是否已被 GC 回收(WeakReferenceMessenger 场景)。
- 确认 Receive 方法中的 Dispatcher 调用是否正确。
- 检查消息是否在正确的线程发送(跨线程发送需要考虑线程安全)。
复盘问题
- 何时应该用 Messenger,何时应该用共享服务或直接引用?
- Messenger 的消息处理顺序是否影响业务逻辑正确性?
- 如何在不打断点的情况下追踪消息的发布和接收?
- 如何设计消息类型体系,避免消息爆炸式增长?
