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

为何Task.WhenAny中总是最后创建的任务率先完成?

为何取消令牌触发时,最后创建的等待任务总是最先完成?

不管怎么调整Task.WhenAny方法里signal1Task、signal2Task、signal3Task的参数顺序,最终完成的任务始终是signal3Task。但如果改变这三个任务的创建顺序,结果就会跟着变:

  • 交换第1行和第3行代码,无论Task.WhenAny的参数顺序如何,完成的任务都是signal1Task;
  • 交换第2行和第3行代码,完成的任务则始终是signal2Task。

这只是巧合吗?

static async Task Main()
{
    CancellationTokenSource cancelTokenSource = new();
    
    AsyncManualResetEvent signal1 = new();
    AsyncAutoResetEvent signal2 = new();
    AsyncAutoResetEvent signal3 = new();
    // 1.
    var signal1Task = signal1.WaitAsync(cancelTokenSource.Token);
    // 2.
    var signal2Task = signal2.WaitAsync(cancelTokenSource.Token);
    // 3.
    var signal3Task = signal3.WaitAsync(cancelTokenSource.Token);
    
    Task.Run(async () =>
    {
        await Task.Delay(1000);
        cancelTokenSource.Cancel();
    });
    
    var completedTask = await Task.WhenAny(signal1Task, signal2Task, signal3Task);       
    if (completedTask == signal1Task)
        Console.WriteLine("signal1Task");
    if (completedTask == signal2Task)
        Console.WriteLine("signal2Task");
    if (completedTask == signal3Task)
        Console.WriteLine("signal3Task");
}

这绝对不是巧合,核心原因在于CancellationToken触发取消时,回调的执行顺序是「后进先出」(LIFO):

当你调用WaitAsync(cancellationToken)时,方法内部会向传入的CancellationToken注册一个取消回调。当Cancel()被调用时,CancellationToken会按照回调注册的逆序来执行——也就是最后注册的回调会被最先触发。

在你的代码里:

  1. signal1Task第一个注册取消回调;
  2. signal2Task第二个注册;
  3. signal3Task最后一个注册。

所以当1秒后Cancel()执行时,signal3Task的回调会被最先调用,它对应的任务也就最先完成,Task.WhenAny自然会返回这个任务,和你在Task.WhenAny里传入的参数顺序完全无关。

同理,如果你调整任务的创建顺序,最后创建的那个任务的回调会被最先触发,它就会成为Task.WhenAny返回的第一个完成任务。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 09:35:17