本篇定位
消息系统最复杂的部分。读完你会理解:
- 内部为什么不用
Dictionary 而是用 Dictionary2?
ConditionalWeakTable2 如何实现”接收者被回收 → 自动取消订阅”?
Send 方法如何用 Span<T> 和 ArrayPool 实现零分配广播?
IRecipient<T> 的 null 标记如何零开销调用?
1. 示例一:页面间通信
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 38 39 40 41 42 43 44 45 46 47 48 49 50
| public sealed class NavigationMessage { public string TargetView { get; } public NavigationMessage(string target) => TargetView = target; }
public class MainViewModel { private readonly IMessenger _messenger;
public MainViewModel() { _messenger = WeakReferenceMessenger.Default; }
public void NavigateTo(string viewName) { _messenger.Send(new NavigationMessage(viewName)); } }
public class ShellViewModel { private string _currentView = "Home"; public string CurrentView { get => _currentView; set => SetProperty(ref _currentView, value); }
public ShellViewModel() { WeakReferenceMessenger.Default.Register<NavigationMessage>(this, (r, m) => { ((ShellViewModel)r).CurrentView = m.TargetView; }); }
~ShellViewModel() { Console.WriteLine("ShellViewModel 被回收,消息订阅已自动解除"); } }
|
为什么不泄漏?
1 2 3 4 5 6 7 8
| SomeService.SomeEvent += ShellViewModel.Handler;
WeakReferenceMessenger.Default.Register<NavigationMessage>(this, Handler);
|
2. 示例二:带数据的消息传递
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 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| public sealed class UserLoggedInMessage { public int UserId { get; } public string UserName { get; } public DateTime LoginTime { get; }
public UserLoggedInMessage(int userId, string userName) { UserId = userId; UserName = userName; LoginTime = DateTime.Now; } }
public class LoginViewModel : ObservableObject { public void DoLogin(string name) {
WeakReferenceMessenger.Default.Send( new UserLoggedInMessage(42, name)); } }
public class NavBarViewModel : IRecipient<UserLoggedInMessage> { private string _userDisplay = "未登录"; public string UserDisplay { get => _userDisplay; set => _userDisplay = value; }
public NavBarViewModel() { WeakReferenceMessenger.Default.RegisterAll(this); }
public void Receive(UserLoggedInMessage message) { UserDisplay = $"{message.UserName} (ID: {message.UserId}) - {message.LoginTime:T}"; } }
public class StatViewModel : IRecipient<UserLoggedInMessage> { private int _totalLogins; public int TotalLogins { get => _totalLogins; set => _totalLogins = value; }
public StatViewModel() { WeakReferenceMessenger.Default.RegisterAll(this); }
public void Receive(UserLoggedInMessage message) { TotalLogins++; Console.WriteLine($"用户 {message.UserName} 登录 (#{TotalLogins})"); } }
|
3. 示例三:带 Token 的频道通信
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 38 39 40 41 42 43
| public sealed class StatusMessage { public string Text { get; } public StatusMessage(string text) => Text = text; }
public class StatusService { public void SendError(string msg) { WeakReferenceMessenger.Default.Send( new StatusMessage(msg), "Error"); }
public void SendInfo(string msg) { WeakReferenceMessenger.Default.Send( new StatusMessage(msg), "Info"); } }
public class ErrorPanelViewModel { private string _lastError = ""; public string LastError { get => _lastError; set => _lastError = value; }
public ErrorPanelViewModel() { WeakReferenceMessenger.Default.Register<StatusMessage, string>( this, "Error", (r, m) => { ((ErrorPanelViewModel)r).LastError = m.Text; }); } }
|
4. 内部数据结构
1 2 3 4 5 6
| public sealed class WeakReferenceMessenger : IMessenger { private readonly Dictionary2<Type2, ConditionalWeakTable2<object, object?>> recipientsMap = new(); }
|
recipientsMap 结构图解:
1 2 3 4 5 6 7 8 9 10
| Type2(NavigationMessage, Unit) └─ ConditionalWeakTable2 ├─ VM_A (弱引用) → null ← IRecipient<T> 标记 └─ VM_B (弱引用) → MessageHandlerDispatcher
Type2(StatusMessage, string) └─ ConditionalWeakTable2 └─ VM_C (弱引用) → Dictionary2<string, object?> ├─ "Error" → MessageHandlerDispatcher └─ "Info" → MessageHandlerDispatcher
|
5. Register 源码流程
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
| private void Register<TMessage, TToken>( object recipient, TToken token, MessageHandlerDispatcher? dispatcher) { lock (this.recipientsMap) { Type2 type2 = new(typeof(TMessage), typeof(TToken)); ref var mapping = ref this.recipientsMap.GetOrAddValueRef(type2); mapping ??= new ConditionalWeakTable2<object, object?>();
if (typeof(TToken) == typeof(Unit)) { if (!mapping.TryAdd(recipient, dispatcher)) throw new InvalidOperationException("重复注册"); } else { var map = Unsafe.As<Dictionary2<TToken, object?>>( mapping.GetValue(recipient, _ => new Dictionary2<TToken, object?>>())!); ref object? registeredHandler = ref map.GetOrAddValueRef(token); if (registeredHandler is not null) throw new InvalidOperationException("重复注册"); registeredHandler = dispatcher; } } }
|
6. Send 源码流程
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 38 39 40 41
| public TMessage Send<TMessage, TToken>(TMessage message, TToken token) { ArrayPoolBufferWriter<object?> bufferWriter; int i = 0;
lock (this.recipientsMap) { Type2 type2 = new(typeof(TMessage), typeof(TToken)); if (!this.recipientsMap.TryGetValue(type2, out var table)) return message;
bufferWriter = ArrayPoolBufferWriter<object?>.Create();
using var enumerator = table.GetEnumerator(); while (enumerator.MoveNext()) { if (typeof(TToken) == typeof(Unit)) { bufferWriter.Add(enumerator.GetValue()); bufferWriter.Add(enumerator.GetKey()); i++; } else { var map = Unsafe.As<Dictionary2<TToken, object?>>(enumerator.GetValue()!); if (map.TryGetValue(token, out object? handler)) { bufferWriter.Add(handler); bufferWriter.Add(enumerator.GetKey()); i++; } } } }
try { SendAll(bufferWriter.Span, i, message); } finally { bufferWriter.Dispose(); }
return message; }
|
为什么要在锁外广播? 防止死锁——接收者可能在 Handler 中再次 Send。
7. SendAll 零开销广播
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| [MethodImpl(MethodImplOptions.NoInlining)] internal static void SendAll<TMessage>( ReadOnlySpan<object?> pairs, int i, TMessage message) { ReadOnlySpan<object?> slice = pairs.Slice(0, 2 * i);
ref object? start = ref MemoryMarshal.GetReference(slice); ref object? end = ref Unsafe.Add(ref start, slice.Length);
while (Unsafe.IsAddressLessThan(ref start, ref end)) { object? handler = start; object recipient = Unsafe.Add(ref start, 1)!;
if (handler is null) Unsafe.As<IRecipient<TMessage>>(recipient).Receive(message); else Unsafe.As<MessageHandlerDispatcher>(handler).Invoke(recipient, message);
start = ref Unsafe.Add(ref start, 2); } }
|
8. 三种注册方式的性能对比
1 2 3 4 5 6 7 8 9 10 11 12 13
| WeakReferenceMessenger.Default.Register<MyMessage>(this, (r, m) => { });
WeakReferenceMessenger.Default.Register<MyMessage>(this, static (r, m) => ((MyVM)r).Handle(m));
public class MyVM : IRecipient<MyMessage> { public MyVM() => WeakReferenceMessenger.Default.RegisterAll(this); public void Receive(MyMessage message) { } }
|
| 方式 |
分配 |
调用开销 |
| Lambda |
委托 + 闭包 |
委托间接调用 |
| 静态方法 |
静态缓存委托 |
委托间接调用 |
| IRecipient |
零 |
直接虚方法调用 |
下一篇预告:Source Generators 源码分析 — 编译时生成如何消灭样板代码