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会浪费线程资源,不符合异步编程规范。
修复方案
- 调整
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); }
- 不要用
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
相关产品推荐
相关产品推荐

