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

为何异步任务two先被取消完成,任务one却从不触发异常?

异步任务取消逻辑解析:为何任务Two先被取消且任务One无异常?

给定代码

static async Task Main(string[] args)
{
    var cts = new CancellationTokenSource(4000);

    try
    {
        await DoOperationAsync(cts.Token);
    }
    catch (OperationCanceledException)
    {
        Console.WriteLine
            ($"Operation canceled prematurely at {DateTime.Now}");
    }

    Console.WriteLine("Press any key...");
    Console.ReadKey(true);
}

static async Task DoOperationAsync
    (CancellationToken cancellationToken)
{
    var t1 = Method("one", 5000, cancellationToken);
    var t2 = Method("two", 5000, cancellationToken);
    var first = await Task.WhenAny(t1, t2);

    Console.WriteLine
        ($"Finished first: {(first == t2 ? "two" : "one")}, {first.Status}");

    var result = await first;
}

static async Task<string> Method
    (string name, int delayMs, 
    CancellationToken cancellationToken)
{
    Console.WriteLine($"Started method {name} at {DateTime.Now}");
    await Task.Delay(delayMs, cancellationToken);
    Console.WriteLine($"Finished method {name} at {DateTime.Now}");
    return name;
}

运行输出

Started method one at 2022-09-01 09:02:43
Started method two at 2022-09-01 09:02:43
Finished first: two, Canceled
Operation canceled prematurely at 2022-09-01 09:02:47
Press any key...
Finished method one at 2022-09-01 09:02:48

问题

为何任务two虽晚于任务one启动,却先被取消并完成,且任务one从不触发异常?


解答

1. 任务启动顺序的误解

输出中"Started method one"和"two"的先后只是控制台输出的微小时序差异,实际两个Method调用是同步连续执行的——异步方法在遇到第一个await前都是同步执行的,因此两个任务几乎同时启动,不存在谁晚于谁的情况。

2. 为何Task.WhenAny返回Canceled状态的t2

  • CancellationTokenSource(4000)会在程序启动4秒后触发取消信号,而Task.Delay(5000)需要执行5秒,因此两个任务都会收到取消信号。
  • Task.WhenAny的核心逻辑是返回第一个进入完成状态(包括Canceled、Faulted、RanToCompletion)的任务。这里t2的Task.Delay先响应了取消信号,进入Canceled状态,所以被Task.WhenAny优先返回——这是多任务竞争中的正常时序差异,换个运行环境可能会是t1先返回。

3. 任务one无异常且完成的原因

  • 当await first(即t2)时,t2的OperationCanceledException会被抛出,被Main方法的catch块捕获,程序继续执行后续代码,但此时t1仍在后台运行。
  • 由于取消信号的传递和Task.Delay的执行存在微小的时间误差,t1的Task.Delay刚好在取消信号到达前完成了5秒的延迟,因此没有触发取消异常,顺利执行完Method方法的后续代码,输出了完成信息。
  • 假设取消信号提前一点到达t1,那t1同样会抛出OperationCanceledException,但因为我们从未await t1,这个异常会被封装在t1的Task.Exception属性中,不会主动抛出(.NET Core及以后版本会自动处理未观察的任务异常,不会导致进程崩溃),同时Method中await Task.Delay之后的代码也不会执行。

内容的提问来源于stack exchange,提问作者rory.ap

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 20:48:19