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

聊一聊 .NET 中的 CancellationTokenSource

一:背景

1. 讲故事

.NET高级调试中,我们需要知道很多的C#底层细节,如果搞不清这些底层细节,那与之相关的故障可能就搞不定,所以调试这个东西需要我们有一个比较广的知识面,痛苦哈,比如这篇跟大家聊到的 CancellationTokenSource 。

二:CancellationTokenSource 分析

1. 一个简单的案例

.NET SDK框架代码中有大量的 CancellationTokenSource 应用,也是被遗弃的Thread.Abort的替代品,为了方便讲述,先写一段简单的代码,通过CancelAfter 让执行流在 2s 后实现中断,参考代码如下:

static void Main(){var cts = new CancellationTokenSource();// 注册取消回调cts.Token.Register(() => { Console.WriteLine("1. 取消回调被执行..."); });cts.Token.Register(() => Console.WriteLine("2. 取消回调被执行..."));cts.CancelAfter(2000); // 2秒后自动取消Console.WriteLine("任务开始,2秒后自动取消...");try{for (int i = 0; i < 10; i++){cts.Token.ThrowIfCancellationRequested();Console.WriteLine($"处理 {i}");Thread.Sleep(500);}}catch (OperationCanceledException){Console.WriteLine("任务被取消!");}Console.ReadKey();}

代码看起来好像是这么一回事,但很少人知道 Register,CancelAfter 底层到底都发生了什么?这也是本篇需要探索的东西,为了能够让大家手握地图,我花了点时间看了下代码画了如下的架构图,截图如下:

2. Token.Register 底层发生了什么

根据地图描述,每一个 Register 函数都被封装成一个 CallbackNode 节点,并最终构建出一个 双向链表,这个链表的头节点会记录到 Registrations.Callbacks 字段上,简化后的代码如下:


internal CancellationTokenRegistration Register(Delegate callback, object stateForCallback, SynchronizationContext syncContext, ExecutionContext executionContext)
{if (!this.IsCancellationRequested){long id = 0L;if (callbackNode == null){callbackNode = new CancellationTokenSource.CallbackNode(registrations);callbackNode.Callback = callback;callbackNode.CallbackState = stateForCallback;callbackNode.ExecutionContext = executionContext;callbackNode.SynchronizationContext = syncContext;registrations.EnterLock();try{CancellationTokenSource.CallbackNode callbackNode3 = callbackNode;CancellationTokenSource.Registrations registrations3 = registrations;long nextAvailableId = registrations3.NextAvailableId;registrations3.NextAvailableId = nextAvailableId + 1L;id = (callbackNode3.Id = nextAvailableId);callbackNode.Next = registrations.Callbacks;if (callbackNode.Next != null){callbackNode.Next.Prev = callbackNode;}registrations.Callbacks = callbackNode;}finally{registrations.ExitLock();}}
}

接下来就是如何眼见为实?可以使用 dnspy 来调试,在 registrations.ExitLock(); 处下一个断点,截图如下:

从卦中可以看到如下信息:

  1. callbackNode.id来看,这个链表采用头插法,即注册的Register是后进先出
  2. CallbackState 存放着我们自定义的回调。
  3. NextAvailableId 记录着接下来需要分配的 callbackNode.id

链表构建好之后,接下来就是如何调用了。

3. cts.CancelAfter 底层发生了什么

可以使用 dnspy 调试源代码,观察下如何实现 2s 后自动触发取消操作,简化后核心代码如下:


private void CancelAfter(uint millisecondsDelay)
{ITimer timer = this._timer;if (timer == null){timer = new TimerQueueTimer(CancellationTokenSource.s_timerCallback, this, uint.MaxValue, uint.MaxValue, false);}timer.Change((millisecondsDelay == uint.MaxValue) ? Timeout.InfiniteTimeSpan : TimeSpan.FromMilliseconds(millisecondsDelay), Timeout.InfiniteTimeSpan);
}private static readonly TimerCallback s_timerCallback = delegate (object obj)
{((CancellationTokenSource)obj).NotifyCancellation(throwOnFirstException: false);
};

从卦中可以看到,所谓的 CancelAfter(2000) 是用Timer定时器来实现的,时间一到自会执行 s_timerCallback 回调函数。

接下来继续研究下内部的 NotifyCancellation 方法,根据前面的分析应该就是把 Registrations.Callbacks 中的节点全部提取出来,简化后的核心代码如下:


private void ExecuteCallbackHandlers(bool throwOnFirstException)
{registrations.ThreadIDExecutingCallbacks = Environment.CurrentManagedThreadId;for (; ; ){registrations.EnterLock();CancellationTokenSource.CallbackNode callbacks;try{callbacks = registrations.Callbacks;if (callbacks == null){break;}if (callbacks.Next != null){callbacks.Next.Prev = null;}registrations.Callbacks = callbacks.Next;registrations.ExecutingCallbackId = callbacks.Id;callbacks.Id = 0L;}finally{registrations.ExitLock();}callbacks.ExecuteCallback();}
}public void ExecuteCallback()
{ExecutionContext.RunInternal(executionContext, delegate (object s){CancellationTokenSource.CallbackNode callbackNode = (CancellationTokenSource.CallbackNode)s;CancellationTokenSource.Invoke(callbackNode.Callback, callbackNode.CallbackState, callbackNode.Registrations.Source);}, this);
}

从卦中代码可以提取到如下几点信息。

  1. ThreadIDExecutingCallbacks 这是一个很好的统计字段,记录着当前谁正在执行 Cancel 方法。
  2. ExecutingCallbackId 同样一个很好的统计字段,记录着从链表 registrations.Callbacks 中已提取出来的 Node 信息。
  3. for 循环一次性的提取 registrations.Callbacks 中的所有节点。

最后我们用 dnspy 在 callbacks.ExecuteCallback() 函数末尾处下一个断点,截图如下:

从卦中可以看到,Callbacks已被清空,最后一个函数节点是 CallbackNode.id=1 ,并且执行这个 Cancel() 方法的线程是5号线程。

三:总结

如今越来越多的底层方法加上了 CancellationTokenSource 取消机制以及 CompositeChangeToken,一旦开发者使用不当导致底层产生了卡死,死锁等一系列问题时,对我们调试者来说真的是亚历山大。


文章转载自:

http://3HVTZiWo.rcfwr.cn
http://wkDgho9W.rcfwr.cn
http://9SVittFZ.rcfwr.cn
http://wGrh2upO.rcfwr.cn
http://g0RHgHwC.rcfwr.cn
http://qofVJlOP.rcfwr.cn
http://czkaNHso.rcfwr.cn
http://3rxBjzv5.rcfwr.cn
http://4cZFSpUj.rcfwr.cn
http://wK1xyram.rcfwr.cn
http://GKSzWO7O.rcfwr.cn
http://EnnhKvEN.rcfwr.cn
http://3sYXrHeh.rcfwr.cn
http://RNG9pZIU.rcfwr.cn
http://RdzG2936.rcfwr.cn
http://8OVmC66d.rcfwr.cn
http://ONSI2r4S.rcfwr.cn
http://eek88vLU.rcfwr.cn
http://uaTHLcbR.rcfwr.cn
http://DrHyVW5c.rcfwr.cn
http://gozqR2ZB.rcfwr.cn
http://9ju9Ly2Y.rcfwr.cn
http://bQVbhqqK.rcfwr.cn
http://tbhkWl0s.rcfwr.cn
http://KbdN7xmS.rcfwr.cn
http://1VdaXh00.rcfwr.cn
http://5m1Qhvv6.rcfwr.cn
http://tqkctuvm.rcfwr.cn
http://MoKa7a7F.rcfwr.cn
http://8sWyn4UK.rcfwr.cn
http://www.dtcms.com/a/367921.html

相关文章:

  • Ubuntu 22 redis集群搭建
  • 开发环境 之 编辑器、编译器、IDE梳理
  • adobe acrobat 安装到使用再到PDF编辑【适合小白,只看一篇就够!!!】
  • [VF2] Boot Ubuntu和Debian发行版
  • 模型剪枝----ResNet18剪枝实战
  • CSS Position 属性
  • 【Android】制造一个ANR并进行简单分析
  • 《sklearn机器学习——回归指标1》
  • 使用tomcat本地部署draw.io
  • C++《C++11》(上)
  • XR数字融合工作站打造智能制造专业学习新范式
  • windows通过xrdp远程连接Ubuntu黑屏问题解决
  • 第25节:VR基础与WebXR API入门
  • Vue-25-利用Vue3大模型对话框设计之前端和后端的基础实现
  • 沪深300股指期权包含上证50期权吗?
  • webhook使用
  • AMD KFD驱动技术分析16:SVM Aperture
  • linux Nginx服务配置介绍,和配置流程
  • 数字人源头厂商实力全揭秘,系统搭建能力盘点!
  • LangChain: Models, Prompts 模型和提示词
  • 【自动化实战】Python操作Excel/WORD/PDF:openpyxl与docx库详解
  • AI急速搭建网站:Gemini、Bolt或Jules、GitHub、Cloudflare Pages实战全流程!
  • Oracle到ClickHouse:异构数据库ETL的坑与解法
  • Spring Boot 参数校验全攻略:从基础到进阶
  • AI架构师的新工具箱:DeepSeek、Copilot、AutoML
  • Go语言实现以太坊Web3开发
  • 新后端漏洞(上)- Aapache Tomcat AJP 文件包含漏洞(CVE-2020-1938)
  • uni-app 和 uni-app x 的区别
  • 手把手教你用Go打造带可视化的网络爬虫
  • 极致效率:用 Copilot 加速你的 Android 开发