聊一聊 .NET 中的 CompositeChangeToken

360影视 动漫周边 2025-09-09 09:20 1

摘要:上一篇跟大家聊到了CancellationTokenSource,今天跟大家聊到的是另一个话题叫组合变更令牌CompositeChangeToken,当前我所有的研究都是基于dump分析之用,所以偏重的点自然就不一样,如果纯纯的研究源码那可能就是入门到放弃。。

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

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

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

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

///
/// 炸弹变化令牌 - 任何炸弹爆炸都会导致房子倒塌
///
publicclassBombChangeToken : IChangeToken
{
private CancellationTokenSource _cts = new CancellationTokenSource;
privatestring _bombName;

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

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

publicbool HasChanged => _cts.IsCancellationRequested;

publicbool ActiveChangeCallbacks => true;

public IDisposable RegisterChangeCallback(Actionobject> callback, object state)
{
return _cts.Token.Register(callback, state);
}
}
}

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

1. RegisterChangeCallback 干了什么

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

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

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

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


public IDisposable RegisterChangeCallback(Actionobject> callback, object state)
{
this.EnsureCallbacksInitialized;
returnthis._cancellationTokenSource.Token.Register(callback, state); //第二件事
}

private void EnsureCallbacksInitialized
{
if (!this.RegisteredCallbackProxy)
{
this._cancellationTokenSource = new CancellationTokenSource;
this._disposables = new List
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字段值是解决程序中疑难杂症的关键。

来源:opendotnet

相关推荐