找回密码
 立即注册
首页 业界区 业界 聊一聊 .NET 中的 CompositeChangeToken

聊一聊 .NET 中的 CompositeChangeToken

啤愿 6 小时前
一:背景

1. 讲故事

上一篇跟大家聊到了 CancellationTokenSource,今天跟大家聊到的是另一个话题叫组合变更令牌 CompositeChangeToken,当前我所有的研究都是基于dump分析之用,所以偏重的点自然就不一样,如果纯纯的研究源码那可能就是入门到放弃。。。接下来说下 CompositeChangeToken是干什么用的,你可以理解成观察者模式,举例:如果一个房子里面有几颗炸弹,只要任何一颗炸弹爆炸,房子都会塌掉,任何关注这个房子的人都会有所变化(跑,叫,哭)... ,其中 CompositeChangeToken 就是观察者集合,有了这个概念之后写一段简单的代码。
  1. namespace BombHouseExample
  2. {
  3.     internal class Program
  4.     {
  5.         static void Main(string[] args)
  6.         {
  7.             // 创建多个炸弹(变化令牌)
  8.             var bomb1 = new BombChangeToken("炸弹1");
  9.             var bomb2 = new BombChangeToken("炸弹2");
  10.             var bomb3 = new BombChangeToken("炸弹3");
  11.             // 创建组合令牌 - 任何炸弹爆炸都会触发房子倒塌
  12.             var houseToken = new CompositeChangeToken(new IChangeToken[] { bomb1, bomb2, bomb3 });
  13.             Console.WriteLine("房子里有几颗炸弹,任何一颗爆炸都会导致房子倒塌!");
  14.             Console.WriteLine("观察者(回调)已注册:当房子倒塌时会有不同反应...\n");
  15.             // 注册不同的观察者反应
  16.             houseToken.RegisterChangeCallback(_ =>
  17.             {
  18.                 Console.WriteLine($"【{DateTime.Now:HH:mm:ss}】 观察者1: 惊声尖叫!");
  19.             }, null);
  20.             houseToken.RegisterChangeCallback(_ =>
  21.             {
  22.                 Console.WriteLine($"【{DateTime.Now:HH:mm:ss}】 观察者2: 拼命逃跑!");
  23.             }, null);
  24.             houseToken.RegisterChangeCallback(_ =>
  25.             {
  26.                 Console.WriteLine($"【{DateTime.Now:HH:mm:ss}】 观察者3: 放声大哭!");
  27.             }, null);
  28.             // 模拟炸弹爆炸
  29.             Console.WriteLine("\n3秒后炸弹爆炸...");
  30.             Thread.Sleep(3000);
  31.             bomb1.Explode();        // 任何一颗炸弹爆炸都会触发所有回调
  32.             Console.WriteLine("\n按任意键退出...");
  33.             Console.ReadKey();
  34.         }
  35.     }
  36.     /// <summary>
  37.     /// 炸弹变化令牌 - 任何炸弹爆炸都会导致房子倒塌
  38.     /// </summary>
  39.     public class BombChangeToken : IChangeToken
  40.     {
  41.         private CancellationTokenSource _cts = new CancellationTokenSource();
  42.         private string _bombName;
  43.         public BombChangeToken(string bombName)
  44.         {
  45.             _bombName = bombName;
  46.         }
  47.         /// <summary>
  48.         /// 引爆炸弹
  49.         /// </summary>
  50.         public void Explode()
  51.         {
  52.             Console.WriteLine($"【{_bombName}】爆炸了!");
  53.             _cts.Cancel();
  54.         }
  55.         public bool HasChanged => _cts.IsCancellationRequested;
  56.         public bool ActiveChangeCallbacks => true;
  57.         public IDisposable RegisterChangeCallback(Action<object> callback, object state)
  58.         {
  59.             return _cts.Token.Register(callback, state);
  60.         }
  61.     }
  62. }
复制代码
1.png

从卦中看,是不是非常的形象,当然这不是本篇的主题,接下来简单研究下底层。
二:CompositeChangeToken 分析

1. RegisterChangeCallback 干了什么

这个方法在底层会做两件事情。

  • 在BombChangeToken 中注入CompositeChangeToken.OnChange 方法
  • 在 CompositeChangeToken 中注入 用户自定义回调。
千言万语之前先来一张类图,我花了点时间自己研究的,不知道对不对哈。
2.png

手握地图接下来就是如何眼见为实呢?先观察源代码。
  1. public IDisposable RegisterChangeCallback(Action<object> callback, object state)
  2. {
  3.     this.EnsureCallbacksInitialized();
  4.     return this._cancellationTokenSource.Token.Register(callback, state);       //第二件事
  5. }
  6. private void EnsureCallbacksInitialized()
  7. {
  8.     if (!this.RegisteredCallbackProxy)
  9.     {
  10.         this._cancellationTokenSource = new CancellationTokenSource();
  11.         this._disposables = new List<IDisposable>();
  12.         for (int i = 0; i < this.ChangeTokens.Count; i++)
  13.         {
  14.             if (this.ChangeTokens[i].ActiveChangeCallbacks)
  15.             {
  16.                 IDisposable disposable = this.ChangeTokens[i].RegisterChangeCallback(CompositeChangeToken._onChangeDelegate, this);  //第一件事
  17.                 if (this._cancellationTokenSource.IsCancellationRequested)
  18.                 {
  19.                     disposable.Dispose();
  20.                     break;
  21.                 }
  22.                 this._disposables.Add(disposable);
  23.             }
  24.         }
  25.     }
  26. }
复制代码
有了源代码的陪伴,接下来使用 dnspy 在 bomb.Explode() 处下断点观察,截图如下:
3.png

从卦中可以看到 BombChangeToken 注册的果然是 OnChange,眼尖的朋友会看到这里有一个 CallbackPartition[16], 稍微解释下,它是为了提高并发,将原来的 Registrations 拆分成了 16 个,相当于有 16 个 CallbackNode 分配器。
接下来看另一张图:
4.png

从卦中可以看到这里也有一个自己的 cts,从 Id=3 可以知道里面有 3-1 个CallbackNode,即我们注册的自定义回调。
这里有一个小注意点,多个 BombChangeToken 和 单个 CompositeChangeToken 内部都有自己的 cts,这个在研究的时候不要以为是一个,搞得晕头转向的。
2. Explode 是如何触发的

想了解这个触发过程,画一张序列图如下:
5.png

从卦中可以清晰的看到 BombChangeToken.TriggerChange() -> CompositeChangeToken.OnChange() -> CancellationTokenRegistration.Dispose() 的过程,接下来就是眼见为实环节了,在 CompositeChangeToken.OnChange 处下一个断点观察。
6.png

最后就是要明白 Composite._cancellationTokenSource 中的一些重要调试信息,比如:

  • _threadIDExecutingCallbacks 当前执行取消操作的线程ID。
  • _executingCallbackId 当前正在处理哪一个CallbackNode 节点。
上面的两点信息在高级调试排查中非常重要,截图如下:
7.png

三:总结

到这里就简单的介绍完了,重点留意 _threadIDExecutingCallbacks 和 _executingCallbackId 字段值是解决程序中疑难杂症的关键。
8.jpeg

来源:豆瓜网用户自行投稿发布,如果侵权,请联系站长删除

相关推荐

您需要登录后才可以回帖 登录 | 立即注册