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

Task.WaitAny运行不符合预期 每次返回索引0问题咨询

Task.WaitAny 始终返回索引0的问题原因及修复方案

问题根因

  • 核心错误是 TimeConsumingTask 声明为 async void,且使用 Task.Factory.StartNew 启动异步方法:
    • async void 属于即发即忘的异步方法声明,仅适用于事件处理器场景。你传入 StartNew 的是一个 async void 委托,StartNew 返回的 Task 只会执行到 TimeConsumingTask 的第一个 await(也就是 await Task.Delay(1000 * 60))就会被标记为完成,并不会等待整个耗时任务执行完毕。
    • 这就导致你添加到 _TaskList 中的所有Task都会在添加后几毫秒就变为已完成状态,当列表长度达到3后,每次调用 Task.WaitAny,索引0位置的Task永远是已完成状态,所以每次都返回0,删除索引0后下一个元素补到0的位置,下一次调用依旧返回0,和你观察到的现象完全一致。
  • 次要问题:在异步方法 ExecuteAsync 中使用同步阻塞的 Task.WaitAny 会浪费线程资源,不符合异步编程规范。

修复方案

  1. 调整 TimeConsumingTask 的返回值为 Task,这是异步方法的通用规范:
private async Task TimeConsumingTask()
{
    _logger.LogInformation("TimeConsumingTask started {task} running at: {time}",
        Task.CurrentId, DateTimeOffset.Now);
    await Task.Delay(1000 * 60);
    _logger.LogInformation("TimeConsumingTask completed {task} running at: {time}",
        Task.CurrentId, DateTimeOffset.Now);
}
  1. 不要用 Task.Factory.StartNew 包装异步方法,直接添加异步方法返回的Task到列表,同时将同步的 WaitAny 替换为异步的 WhenAny:
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
    List<Task> _TaskList = new List<Task>();

    while (!stoppingToken.IsCancellationRequested)
    {
        _logger.LogInformation("--------------------------------");

        if (_TaskList.Count >= 3)
        {
            var completedTask = await Task.WhenAny(_TaskList, stoppingToken);
            int iTaskIndex = _TaskList.IndexOf(completedTask);
            
            _logger.LogInformation("iTaskIndex - {0}", iTaskIndex);
            if (iTaskIndex >= 0)
            {
                _TaskList.RemoveAt(iTaskIndex);
            }
        }

        // 直接添加异步方法返回的Task即可,不需要额外包装StartNew/Run
        _TaskList.Add(TimeConsumingTask());

        await Task.Delay(1000, stoppingToken);
    }
}

额外优化建议

如果你只是需要控制并发任务数不超过3,更简单不易出错的方案是使用 SemaphoreSlim 做并发限流,不需要自己维护任务列表和处理索引:

// 声明SemaphoreSlim,最大并发数3
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(3, 3);

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
    while (!stoppingToken.IsCancellationRequested)
    {
        // 等待空闲并发位
        await _semaphore.WaitAsync(stoppingToken);
        // 启动任务,执行完释放信号量
        _ = Task.Run(async () =>
        {
            try
            {
                await TimeConsumingTask();
            }
            finally
            {
                _semaphore.Release();
            }
        }, stoppingToken);

        await Task.Delay(1000, stoppingToken);
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.04 03:12:01