You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#中CancellationToken为何表现出非确定性行为?

关于Task取消行为差异的原因分析

现象总结

  • 将tokenSource.Cancel()移至Task.Run()调用前执行时,程序必然触发OperationCanceledException并进入catch块
  • 保持原代码顺序(先创建Task再调用Cancel)时,程序有时进入catch块,有时会无限循环打印数字

原因拆解

1. Cancel在Task.Run之前执行的场景

当你先调用tokenSource.Cancel(),此时传入Task.Run的CancellationToken已经处于已取消状态。Task.Run在调度任务前会先检查令牌状态:如果令牌已取消,它不会执行传入的委托逻辑,直接返回一个处于已取消状态的Task。当你await这个Task时,框架会自动抛出OperationCanceledException,因此每次都会进入catch块。

2. 原代码顺序的场景

原代码存在线程调度竞态条件,两种结果取决于Cancel调用和任务委托执行的先后:

  • 若tokenSource.Cancel()在任务委托的while循环开始执行之前被调用:Task.Run检测到令牌已取消,不会执行委托,返回已取消的Task,await时抛出异常进入catch块。
  • 若线程池线程已经调度并开始执行while(true)循环:你的委托逻辑里完全没有检查CancellationToken的状态,任务会一直执行无限循环,不会响应任何取消请求,因此会持续打印数字,永远不会进入catch块。

修正方案

要让任务正确响应取消,必须在循环内部主动检查令牌状态,修改后的代码如下:

using System;
using System.Threading;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        var tokenSource = new CancellationTokenSource();
        var task = Task.Run(() =>
        {
            int i = 0;
            while (true)
            {
                // 主动检查令牌,若已取消则抛出异常终止任务
                tokenSource.Token.ThrowIfCancellationRequested();
                Console.WriteLine($"{i++}");
            }
        }, tokenSource.Token);
        
        tokenSource.Cancel();

        try
        {
            await task;
        }
        catch (OperationCanceledException e)
        {
            Console.WriteLine($"{nameof(OperationCanceledException)} thrown with message: {e.Message}");
        }
        finally
        {
            tokenSource.Dispose();
        }

        Console.ReadKey();
    }
}

这样无论Cancel调用时机如何,只要令牌被取消,循环内的检查会触发异常,任务会进入取消状态,await时就能捕获异常。

内容的提问来源于stack exchange,提问作者user33276346

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 03:07:18