使用CancellationTokenSource设置20秒超时,回调计算时间小于设定值的问题
为什么CancellationTokenSource.CancelAfter(20秒)会在不到20秒时触发取消?
我在使用CancellationTokenSource设置20秒超时,通过cts.Token.Register委托计算从启动到取消的时间时,偶尔会得到小于20秒的结果(比如19.999474)。这个问题是随机出现的,Windows系统上复现不了,但在Ubuntu环境里很容易触发。
测试代码
for (int i = 0; i < 50; i++) { var cts = new CancellationTokenSource(); var startTime = DateTime.Now; cts.Token.Register(() => { Console.WriteLine((DateTime.Now - startTime).TotalSeconds); }); cts.CancelAfter(TimeSpan.FromSeconds(20)); Task.Run(async () => { while (!cts.IsCancellationRequested) { await Task.Delay(1000, cts.Token); } }, cts.Token); } Console.WriteLine("Hello, World!"); Console.Read();
Ubuntu下的输出示例
/console# dotnet ConsoleApp.dll Hello, World! 20.021207 19.999749 19.9997085 19.9997404 19.999752 ...(其余输出省略)
环境信息
/console# dotnet --info global.json file: Not found Host: Version: 6.0.15 Architecture: x64 Commit: 5edef4b20b .NET SDKs installed: No SDKs were found. .NET runtimes installed: Microsoft.AspNetCore.App 6.0.15 [/usr/share/dotnet/shared/Microsoft.AspNetCore.App] Microsoft.NETCore.App 6.0.15 [/usr/share/dotnet/shared/Microsoft.NETCore.App]
问题原因
- 系统定时器精度差异:Windows和Linux上的定时器实现机制不同,Linux下的定时器精度和调度策略可能导致取消信号提前几毫秒触发。操作系统的定时器本身就不是绝对精确的,多任务环境下的进程调度延迟会影响触发时机。
- DateTime.Now的精度限制:
DateTime.Now依赖系统时钟,其精度在不同系统上有差异,用来测量短时间间隔时容易出现误差。
解决办法
- 改用Stopwatch测量时间:
Stopwatch基于系统高分辨率计时器,精度远高于DateTime.Now,能更准确地记录时间间隔。修改后的代码如下:
for (int i = 0; i < 50; i++) { var cts = new CancellationTokenSource(); var stopwatch = Stopwatch.StartNew(); cts.Token.Register(() => { stopwatch.Stop(); Console.WriteLine(stopwatch.Elapsed.TotalSeconds); }); cts.CancelAfter(TimeSpan.FromSeconds(20)); Task.Run(async () => { while (!cts.IsCancellationRequested) { await Task.Delay(1000, cts.Token); } }, cts.Token); } Console.WriteLine("Hello, World!"); Console.Read();
- 接受微小误差:操作系统层面无法做到绝对精确的定时,几毫秒的偏差属于正常范围。如果业务逻辑对这几毫秒的误差敏感,可适当调整超时时间(比如设置为20.001秒),但一般情况下无需特殊处理。
内容的提问来源于stack exchange,提问作者Mr. Mantou
相关产品推荐
相关产品推荐

