在.NET高级调试中,我们需要知道很多的C#底层细节,如果搞不清这些底层细节,那与之相关的故障可能就搞不定,所以调试这个东西需要我们有一个比较广的知识面,痛苦哈,比如这篇跟大家聊到的 CancellationTokenSource 。在.NET SDK框架代码中有大量的 CancellationTokenSource 应用,也是被遗弃的Thread.Abort的替代品,为了方便讲述,先写一段简单的代码,通过CancelAfter 让执行流在 2s 后实现中断,参考代码如下:摘要:在.NET高级调试中,我们需要知道很多的C#底层细节,如果搞不清这些底层细节,那与之相关的故障可能就搞不定,所以调试这个东西需要我们有一个比较广的知识面,痛苦哈,比如这篇跟大家聊到的 CancellationTokenSource 。在
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;
}
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;处下一个断点,截图如下:
从卦中可以看到如下信息:
从 callbackNode.id来看,这个链表采用头插法,即注册的Register是后进先出。CallbackState 存放着我们自定义的回调。
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);
}
privatestaticreadonly TimerCallback s_timerCallback = delegate (object obj)
{
((CancellationTokenSource)obj).NotifyCancellation(throwOnFirstException: false);
};
从卦中可以看到,所谓的CancelAfter(2000)是用Timer定时器来实现的,时间一到自会执行s_timerCallback回调函数。接下来继续研究下内部的 NotifyCancellation 方法,根据前面的分析应该就是把中的节点全部提取出来,简化后的核心代码如下:
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);
}
从卦中代码可以提取到如下几点信息。
ThreadIDExecutingCallbacks 这是一个很好的统计字段,记录着当前谁正在执行 Cancel 方法。
ExecutingCallbackId 同样一个很好的统计字段,记录着从链表 registrations.Callbacks 中已提取出来的 Node 信息。
for 循环一次性的提取 registrations.Callbacks 中的所有节点。
最后我们用 dnspy 在函数末尾处下一个断点,截图如下:如今越来越多的底层方法加上了 CancellationTokenSource 取消机制以及 CompositeChangeToken,一旦开发者使用不当导致底层产生了卡死,死锁等一系列问题时,对我们调试者来说真的是亚历山大。
来源:opendotnet
免责声明:本站系转载,并不代表本网赞同其观点和对其真实性负责。如涉及作品内容、版权和其它问题,请在30日内与本站联系,我们将在第一时间删除内容!