当前位置: 首页 > news >正文

聊一聊 .NET 中的 CompositeChangeToken

一:背景

1. 讲故事

上一篇跟大家聊到了 CancellationTokenSource,今天跟大家聊到的是另一个话题叫组合变更令牌 CompositeChangeToken,当前我所有的研究都是基于dump分析之用,所以偏重的点自然就不一样,如果纯纯的研究源码那可能就是入门到放弃。。。接下来说下 CompositeChangeToken是干什么用的,你可以理解成观察者模式,举例:如果一个房子里面有几颗炸弹,只要任何一颗炸弹爆炸,房子都会塌掉,任何关注这个房子的人都会有所变化(跑,叫,哭)… ,其中 CompositeChangeToken 就是观察者集合,有了这个概念之后写一段简单的代码。


namespace BombHouseExample
{internal class Program{static void Main(string[] args){// 创建多个炸弹(变化令牌)var bomb1 = new BombChangeToken("炸弹1");var bomb2 = new BombChangeToken("炸弹2");var bomb3 = new BombChangeToken("炸弹3");// 创建组合令牌 - 任何炸弹爆炸都会触发房子倒塌var houseToken = new CompositeChangeToken(new IChangeToken[] { bomb1, bomb2, bomb3 });Console.WriteLine("房子里有几颗炸弹,任何一颗爆炸都会导致房子倒塌!");Console.WriteLine("观察者(回调)已注册:当房子倒塌时会有不同反应...\n");// 注册不同的观察者反应houseToken.RegisterChangeCallback(_ =>{Console.WriteLine($"【{DateTime.Now:HH:mm:ss}】 观察者1: 惊声尖叫!");}, null);houseToken.RegisterChangeCallback(_ =>{Console.WriteLine($"【{DateTime.Now:HH:mm:ss}】 观察者2: 拼命逃跑!");}, null);houseToken.RegisterChangeCallback(_ =>{Console.WriteLine($"【{DateTime.Now:HH:mm:ss}】 观察者3: 放声大哭!");}, null);// 模拟炸弹爆炸Console.WriteLine("\n3秒后炸弹爆炸...");Thread.Sleep(3000);bomb1.Explode();        // 任何一颗炸弹爆炸都会触发所有回调Console.WriteLine("\n按任意键退出...");Console.ReadKey();}}/// <summary>/// 炸弹变化令牌 - 任何炸弹爆炸都会导致房子倒塌/// </summary>public class BombChangeToken : IChangeToken{private CancellationTokenSource _cts = new CancellationTokenSource();private string _bombName;public BombChangeToken(string bombName){_bombName = bombName;}/// <summary>/// 引爆炸弹/// </summary>public void Explode(){Console.WriteLine($"【{_bombName}】爆炸了!");_cts.Cancel();}public bool HasChanged => _cts.IsCancellationRequested;public bool ActiveChangeCallbacks => true;public IDisposable RegisterChangeCallback(Action<object> callback, object state){return _cts.Token.Register(callback, state);}}
}

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

二:CompositeChangeToken 分析

1. RegisterChangeCallback 干了什么

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

  1. BombChangeToken 中注入CompositeChangeToken.OnChange 方法
  2. CompositeChangeToken 中注入 用户自定义回调

千言万语之前先来一张类图,我花了点时间自己研究的,不知道对不对哈。

手握地图接下来就是如何眼见为实呢?先观察源代码。


public IDisposable RegisterChangeCallback(Action<object> callback, object state)
{this.EnsureCallbacksInitialized();return this._cancellationTokenSource.Token.Register(callback, state);       //第二件事
}private void EnsureCallbacksInitialized()
{if (!this.RegisteredCallbackProxy){this._cancellationTokenSource = new CancellationTokenSource();this._disposables = new List<IDisposable>();for (int i = 0; i < this.ChangeTokens.Count; i++){if (this.ChangeTokens[i].ActiveChangeCallbacks){IDisposable disposable = this.ChangeTokens[i].RegisterChangeCallback(CompositeChangeToken._onChangeDelegate, this);  //第一件事if (this._cancellationTokenSource.IsCancellationRequested){disposable.Dispose();break;}this._disposables.Add(disposable);}}}
}

有了源代码的陪伴,接下来使用 dnspy 在 bomb.Explode() 处下断点观察,截图如下:

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

接下来看另一张图:

从卦中可以看到这里也有一个自己的 cts,从 Id=3 可以知道里面有 3-1 个CallbackNode,即我们注册的自定义回调。

这里有一个小注意点,多个 BombChangeToken 和 单个 CompositeChangeToken 内部都有自己的 cts,这个在研究的时候不要以为是一个,搞得晕头转向的。

2. Explode 是如何触发的

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

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

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

  • _threadIDExecutingCallbacks 当前执行取消操作的线程ID。
  • _executingCallbackId 当前正在处理哪一个CallbackNode 节点。

上面的两点信息在高级调试排查中非常重要,截图如下:

三:总结

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


文章转载自:

http://HDXty76N.qtqjx.cn
http://wZMpnyZA.qtqjx.cn
http://mcJTlahy.qtqjx.cn
http://ae8onkMT.qtqjx.cn
http://jZxU2SAV.qtqjx.cn
http://c0p3MRvC.qtqjx.cn
http://HVIgeeAH.qtqjx.cn
http://RwCXK4RS.qtqjx.cn
http://yKkojiV8.qtqjx.cn
http://ft8B9LJM.qtqjx.cn
http://F7b44qWs.qtqjx.cn
http://yTh3wf0m.qtqjx.cn
http://EpdgFB0z.qtqjx.cn
http://Cv4GZk7j.qtqjx.cn
http://goh0PRCw.qtqjx.cn
http://rQq3sbdd.qtqjx.cn
http://MxXA9zEu.qtqjx.cn
http://0Yyz7JFF.qtqjx.cn
http://8wUHi7e2.qtqjx.cn
http://OOZtg4vD.qtqjx.cn
http://dftRFhm3.qtqjx.cn
http://p0HnW4mD.qtqjx.cn
http://DLISBHMq.qtqjx.cn
http://BZPzlPmi.qtqjx.cn
http://uL8xV8Xi.qtqjx.cn
http://NMOrTW0x.qtqjx.cn
http://uezFvQ1C.qtqjx.cn
http://39j6IEBX.qtqjx.cn
http://3uGkMqe7.qtqjx.cn
http://Fhb0zmHp.qtqjx.cn
http://www.dtcms.com/a/373225.html

相关文章:

  • 视觉语言模型应用开发——Qwen 2.5 VL模型视频理解与定位能力深度解析及实践指南
  • 深入理解 MDC(Mapped Diagnostic Context):日志记录的利器
  • 工业相机如何通过光度立体成像技术实现高效精准的2.5D缺陷检测
  • qt+halcon开发相机拍照软件步骤
  • cs61A lab01
  • 大数据毕业设计选题推荐-基于大数据的国家医用消耗选品采集数据可视化分析系统-Hadoop-Spark-数据可视化-BigData
  • Oracle APEX 利用卡片实现翻转
  • Spring Security AuthenticationManager 接口详解与实战
  • 人机协同的智慧共生平台:跨学科知识中心暨融智中心,从认知到实践的闭环自动转化
  • AG32 ( MCU+FPGA二合一 )是如何卷入了三相电能计量市场的
  • 2025年- H119-Lc88. 合并两个有序数组(数组)--Java版
  • 树莓派 Ubuntu 24.04 开机换源总结
  • 简单的 k8s 部署分布式Go微服务集群实例
  • 旅行社旅游管理系统的设计与实现(代码+数据库+LW)
  • Three.js shader内置矩阵注入
  • 在公用同一公网IP和端口的K8S环境中,不同域名实现不同访问需求的解决方案
  • 【MFC视图和窗口基础:文档/视图的“双胞胎”魔法 + 单文档程序】
  • Cocos creator3.x 处理 16KB 问题
  • 【MFC文档与视图结构:数据“仓库”与“橱窗”的梦幻联动 + 初始化“黑箱”大揭秘!】
  • 【MFC】对话框属性:Use System Font(使用系统字体)
  • springboot3.3.5 集成elasticsearch8.12.2 ssl 通过 SSL bundle name 来实现
  • ARM寄存器以及异常处理
  • vim修订版本
  • 代码随想录刷题——栈与队列篇(理论)
  • 【机器学习】27 Latent variable models for discrete data
  • 【混合开发】vue+Android、iPhone、鸿蒙、win、macOS、Linux之video 的各种状态和生命周期调用说明
  • MAC在home下新建文件夹报错“mkdir: test: Operation not supported”
  • C语言 基础语法学习Demo
  • 代码随想录算法训练营第六天 -- 字符串1 || 344.反转字符串I / 541.反转字符串II / kamacoder54.替换数字--第八期模拟笔试
  • 设计模式:访问者模式(Visitor Pattern)