一夜暴富的苗苗 發表於 2025-9-8 11:22:00

聊一聊 .NET 中的 CompositeChangeToken

<h2 id="一背景">一:背景</h2>
<h3 id="1-讲故事">1. 讲故事</h3>
<p>上一篇跟大家聊到了 <code>CancellationTokenSource</code>,今天跟大家聊到的是另一个话题叫组合变更令牌 <code>CompositeChangeToken</code>,当前我所有的研究都是基于<code>dump分析</code>之用,所以偏重的点自然就不一样,如果纯纯的研究源码那可能就是入门到放弃。。。接下来说下 CompositeChangeToken是干什么用的,你可以理解成观察者模式,举例:如果一个房子里面有几颗炸弹,只要任何一颗炸弹爆炸,房子都会塌掉,任何关注这个房子的人都会有所变化(跑,叫,哭)... ,其中 CompositeChangeToken 就是观察者集合,有了这个概念之后写一段简单的代码。</p>
<pre><code class="language-C#">
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(_ =&gt;
            {
                Console.WriteLine($"【{DateTime.Now:HH:mm:ss}】 观察者1: 惊声尖叫!");
            }, null);

            houseToken.RegisterChangeCallback(_ =&gt;
            {
                Console.WriteLine($"【{DateTime.Now:HH:mm:ss}】 观察者2: 拼命逃跑!");
            }, null);

            houseToken.RegisterChangeCallback(_ =&gt;
            {
                Console.WriteLine($"【{DateTime.Now:HH:mm:ss}】 观察者3: 放声大哭!");
            }, null);

            // 模拟炸弹爆炸
            Console.WriteLine("\n3秒后炸弹爆炸...");
            Thread.Sleep(3000);
            bomb1.Explode();      // 任何一颗炸弹爆炸都会触发所有回调

            Console.WriteLine("\n按任意键退出...");
            Console.ReadKey();
      }
    }

    /// &lt;summary&gt;
    /// 炸弹变化令牌 - 任何炸弹爆炸都会导致房子倒塌
    /// &lt;/summary&gt;
    public class BombChangeToken : IChangeToken
    {
      private CancellationTokenSource _cts = new CancellationTokenSource();
      private string _bombName;

      public BombChangeToken(string bombName)
      {
            _bombName = bombName;
      }

      /// &lt;summary&gt;
      /// 引爆炸弹
      /// &lt;/summary&gt;
      public void Explode()
      {
            Console.WriteLine($"【{_bombName}】爆炸了!");
            _cts.Cancel();
      }

      public bool HasChanged =&gt; _cts.IsCancellationRequested;

      public bool ActiveChangeCallbacks =&gt; true;

      public IDisposable RegisterChangeCallback(Action&lt;object&gt; callback, object state)
      {
            return _cts.Token.Register(callback, state);
      }
    }
}

</code></pre>
<p><img src="https://img2024.cnblogs.com/blog/214741/202509/214741-20250908112233260-1571318112.png" alt="" loading="lazy"></p>
<p>从卦中看,是不是非常的形象,当然这不是本篇的主题,接下来简单研究下底层。</p>
<h2 id="二compositechangetoken-分析">二:CompositeChangeToken 分析</h2>
<h3 id="1-registerchangecallback-干了什么">1. RegisterChangeCallback 干了什么</h3>
<p>这个方法在底层会做两件事情。</p>
<ol>
<li>在<code>BombChangeToken</code> 中注入<code>CompositeChangeToken.OnChange</code> 方法</li>
<li>在 <code>CompositeChangeToken</code> 中注入 <code>用户自定义回调</code>。</li>
</ol>
<p>千言万语之前先来一张类图,我花了点时间自己研究的,不知道对不对哈。</p>
<p><img src="https://img2024.cnblogs.com/blog/214741/202509/214741-20250908112233277-1331294210.png" alt="" loading="lazy"></p>
<p>手握地图接下来就是如何眼见为实呢?先观察源代码。</p>
<pre><code class="language-C#">
public IDisposable RegisterChangeCallback(Action&lt;object&gt; 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&lt;IDisposable&gt;();
      for (int i = 0; i &lt; this.ChangeTokens.Count; i++)
      {
            if (this.ChangeTokens.ActiveChangeCallbacks)
            {
                IDisposable disposable = this.ChangeTokens.RegisterChangeCallback(CompositeChangeToken._onChangeDelegate, this);//第一件事
                if (this._cancellationTokenSource.IsCancellationRequested)
                {
                  disposable.Dispose();
                  break;
                }
                this._disposables.Add(disposable);
            }
      }
    }
}

</code></pre>
<p>有了源代码的陪伴,接下来使用 dnspy 在 <code>bomb.Explode()</code> 处下断点观察,截图如下:</p>
<p><img src="https://img2024.cnblogs.com/blog/214741/202509/214741-20250908112233272-1766233107.png" alt="" loading="lazy"></p>
<p>从卦中可以看到 BombChangeToken 注册的果然是 <code>OnChange</code>,眼尖的朋友会看到这里有一个 <code>CallbackPartition</code>, 稍微解释下,它是为了提高并发,将原来的 Registrations 拆分成了 16 个,相当于有 16 个 CallbackNode 分配器。</p>
<p>接下来看另一张图:</p>
<p><img src="https://img2024.cnblogs.com/blog/214741/202509/214741-20250908112233295-15664820.png" alt="" loading="lazy"></p>
<p>从卦中可以看到这里也有一个自己的 cts,从 <code>Id=3</code> 可以知道里面有 <code>3-1</code> 个CallbackNode,即我们注册的自定义回调。</p>
<p>这里有一个小注意点,多个 BombChangeToken 和 单个 CompositeChangeToken 内部都有自己的 cts,这个在研究的时候不要以为是一个,搞得晕头转向的。</p>
<h3 id="2-explode-是如何触发的">2. Explode 是如何触发的</h3>
<p>想了解这个触发过程,画一张序列图如下:</p>
<p><img src="https://img2024.cnblogs.com/blog/214741/202509/214741-20250908112233253-1339397779.png" alt="" loading="lazy"></p>
<p>从卦中可以清晰的看到 <code>BombChangeToken.TriggerChange() -&gt; CompositeChangeToken.OnChange() -&gt; CancellationTokenRegistration.Dispose()</code> 的过程,接下来就是眼见为实环节了,在 CompositeChangeToken.OnChange 处下一个断点观察。</p>
<p><img src="https://img2024.cnblogs.com/blog/214741/202509/214741-20250908112233246-1343324151.png" alt="" loading="lazy"></p>
<p>最后就是要明白 <code>Composite._cancellationTokenSource</code> 中的一些重要调试信息,比如:</p>
<ul>
<li>_threadIDExecutingCallbacks 当前执行取消操作的线程ID。</li>
<li>_executingCallbackId 当前正在处理哪一个CallbackNode 节点。</li>
</ul>
<p>上面的两点信息在高级调试排查中非常重要,截图如下:<img src="https://img2024.cnblogs.com/blog/214741/202509/214741-20250908112233267-1428899313.png" alt="" loading="lazy"></p>
<h2 id="三总结">三:总结</h2>
<p>到这里就简单的介绍完了,重点留意 <code>_threadIDExecutingCallbacks</code> 和 <code>_executingCallbackId</code> 字段值是解决程序中疑难杂症的关键。</p>
<img src="https://images.cnblogs.com/cnblogs_com/huangxincheng/345039/o_210929020104最新消息优惠促销公众号关注二维码.jpg" width="700" height="300" alt="图片名称" align="center"><br><br>
来源:https://www.cnblogs.com/huangxincheng/p/19079389
頁: [1]
查看完整版本: 聊一聊 .NET 中的 CompositeChangeToken