惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Microsoft Azure Blog
Microsoft Azure Blog
Cloudbric
Cloudbric
I
InfoQ
V
V2EX
博客园_首页
The Register - Security
The Register - Security
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
S
Secure Thoughts
Vercel News
Vercel News
Forbes - Security
Forbes - Security
云风的 BLOG
云风的 BLOG
PCI Perspectives
PCI Perspectives
L
LINUX DO - 最新话题
D
DataBreaches.Net
H
Hacker News: Front Page
Application and Cybersecurity Blog
Application and Cybersecurity Blog
B
Blog RSS Feed
A
About on SuperTechFans
N
News and Events Feed by Topic
Apple Machine Learning Research
Apple Machine Learning Research
Help Net Security
Help Net Security
Attack and Defense Labs
Attack and Defense Labs
N
Netflix TechBlog - Medium
Spread Privacy
Spread Privacy
F
Full Disclosure
Recorded Future
Recorded Future
AWS News Blog
AWS News Blog
博客园 - 【当耐特】
The Cloudflare Blog
T
Threatpost
T
Tor Project blog
Google DeepMind News
Google DeepMind News
C
CXSECURITY Database RSS Feed - CXSecurity.com
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Recent Announcements
Recent Announcements
M
MIT News - Artificial intelligence
A
Arctic Wolf
C
Check Point Blog
Stack Overflow Blog
Stack Overflow Blog
T
Threat Research - Cisco Blogs
Security Archives - TechRepublic
Security Archives - TechRepublic
Hacker News - Newest:
Hacker News - Newest: "LLM"
WordPress大学
WordPress大学
Cyberwarzone
Cyberwarzone
小众软件
小众软件
C
Cyber Attacks, Cyber Crime and Cyber Security
P
Proofpoint News Feed
Security Latest
Security Latest
The Last Watchdog
The Last Watchdog

博客园 - 一线码农

记一次 .NET 某光电成像后端解析系统 内存暴涨分析 记一次 .NET 某电力后台监控系统 内存暴涨分析 记一次 .NET 某注塑模具系统 CPU爆高分析 记一次 .NET 某集群管理软件 内存暴涨分析 记一次 .NET 某智慧工厂视觉程序 崩溃分析 记一次 .NET 某低代码开发框架 内存暴涨分析 记一次 .NET 某MES上位机拍照系统 内存暴涨分析 记一次 .NET 某RFID标签打印客户端 崩溃分析 对 .NET FileSystemWatcher引发内存碎片化的 反思 DotMemory系列:5. 如何实现自动化抓取和应用自托管 DotMemory系列:4. 如何分析进程的转储文件 DotMemory系列:3. 堆碎片化引发的内存暴涨分析 DotMemory系列:2. 事件泄露引发的内存暴涨分析 DotMemory系列:1. 终结队列积压引发的内存暴涨分析 记一次 .NET 某理财管理客户端 OOM溢出分析 记一次 .NET 某医联体管理系统 崩溃分析 记一次 .NET 某光放测试系统 崩溃分析 记一次 .NET 某药品缺陷高速检测系统 卡慢分析 聊一聊 .NET超高内存故障分析方法 的反思 记一次 .NET 某企业ECM内容管理系统 内存暴涨分析 记一次 .NET 某跨境物流系统 内存暴涨分析 记一次 .NET 某中医药附属医院门诊系统 崩溃分析 聊一聊 .NET 中的 CancellationTokenSource 记一次 .NET 某CRM物流行业管理系统 崩溃分析
聊一聊 .NET 中的 CompositeChangeToken
一线码农 · 2025-09-08 · via 博客园 - 一线码农

一:背景

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 字段值是解决程序中疑难杂症的关键。

图片名称