写点什么

聊一聊 .NET 中的 CompositeChangeToken

作者:秃头小帅oi
  • 2025-09-18
    福建
  • 本文字数:2754 字

    阅读完需:约 9 分钟

一:背景


  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 干了什么这个方法在底层会做两件事情。


在 BombChangeToken 中注入 CompositeChangeToken.OnChange 方法在 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,这个在研究的时候不要以为是一个,搞得晕头转向的。


  1. Explode 是如何触发的想了解这个触发过程,画一张序列图如下:


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


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


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


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

行业拓展

分享一个面向研发人群使用的前后端分离的低代码软件——JNPF

基于 Java Boot/.Net Core 双引擎,它适配国产化,支持主流数据库和操作系统,提供五十几种高频预制组件,内置了常用的后台管理系统使用场景和实用模版,通过简单的拖拉拽操作,开发者能够高效完成软件开发,提高开发效率,减少代码编写工作。

JNPF 基于 SpringBoot+Vue.js,提供了一个适合所有水平用户的低代码学习平台,无论是有经验的开发者还是编程新手,都可以在这里找到适合自己的学习路径。

此外,JNPF 支持全源码交付,完全支持根据公司、项目需求、业务需求进行二次改造开发或内网部署,具备多角色门户、登录认证、组织管理、角色授权、表单设计、流程设计、页面配置、报表设计、门户配置、代码生成工具等开箱即用的在线服务。

用户头像

摸个鱼,顺便发点有用的东西 2023-06-19 加入

互联网某厂人(重生版)

评论

发布
暂无评论
聊一聊 .NET 中的 CompositeChangeToken_秃头小帅oi_InfoQ写作社区