如何判断Task.Delay是正常完成还是被CancellationToken取消
问题核心
你遇到的情况是因为CancellationTokenSource(5000)的5秒超时和Task.Delay(5000)的等待时长完全同步——5秒后两者几乎同时触发,不管Delay是否正常跑完,Token的取消请求状态都会被置为true,自然没法靠这个属性直接区分结果。
以下是几种可行的判断方式:
方法1:用Task.WhenAny区分先完成的任务
把Delay任务和取消信号的等待任务分开,通过Task.WhenAny判断哪个先结束:
var cts = new CancellationTokenSource(5000); // 不带取消令牌的Delay任务,确保它能正常跑完5秒 var delayTask = Task.Delay(5000); // 绑定取消令牌的空等待任务,取消时会触发完成 var cancelTriggeredTask = Task.Delay(Timeout.Infinite, cts.Token); var completedTask = await Task.WhenAny(delayTask, cancelTriggeredTask); if (completedTask == delayTask) { // Delay先完成,说明是正常等待满时长结束 Console.WriteLine("等待满5秒完成"); } else { // 取消信号先触发,说明任务被取消 Console.WriteLine("任务被取消"); }
原理:Task.WhenAny会返回最先完成的任务。如果Delay正常跑完,它会比取消触发的任务先完成(哪怕只差几毫秒);反之如果取消信号提前触发,取消任务会先结束。
方法2:捕获取消异常并结合时间判断
如果必须给Task.Delay绑定取消令牌,可以通过捕获OperationCanceledException,同时记录执行时长辅助判断:
var cts = new CancellationTokenSource(5000); var startTime = DateTime.UtcNow; bool isCompletedNormally = false; try { await Task.Delay(5000, cts.Token); // 能走到这里说明Delay正常完成,未被取消 isCompletedNormally = true; } catch (OperationCanceledException) { // 捕获到取消异常后,计算耗时判断是否接近5秒 var elapsedMs = (DateTime.UtcNow - startTime).TotalMilliseconds; // 留100ms误差范围,避免系统计时精度导致误判 isCompletedNormally = elapsedMs >= 4900; } if (isCompletedNormally) { Console.WriteLine("等待满5秒完成"); } else { Console.WriteLine("任务被取消"); }
注意:这种方法依赖系统计时精度,适合对判断精度要求不高的场景。
方法3:拆分取消逻辑(推荐)
如果业务允许,尽量不要让CancellationTokenSource的超时时间和Task.Delay的时长完全一致,比如给一方调整时间差,这样就能通过是否抛出异常直接判断:
var cts = new CancellationTokenSource(5500); // 超时设为5.5秒 try { await Task.Delay(5000, cts.Token); Console.WriteLine("等待满5秒完成"); } catch (OperationCanceledException) { Console.WriteLine("任务被取消"); }
这种方式最简洁:只要Delay正常跑完,cts还没触发取消,不会抛出异常;只有当Delay未跑完就触发取消时,才会抛出异常。
内容的提问来源于stack exchange,提问作者kosnkov
相关产品推荐
相关产品推荐

