SemaphoreSlim限制异步任务并发不生效,如何正确实现并发数管控?
现有实现的核心问题
你的代码存在三个核心错误直接导致SemaphoreSlim限流失效:
- 任务创建逻辑存在嵌套Task问题:你使用
new Task(async () => {})创建异步任务时,返回的实际是Task<Task>类型的嵌套任务,你将其赋值给非泛型的Task Operation属性后,外层Task仅代表异步委托的同步部分执行完成,只要内部逻辑碰到第一个await就会标记为完成,不会等待整个业务逻辑执行结束。 - 信号量释放时机错误:你在「启动任务」的动作完成后就立刻调用了
Release()释放信号量,而非等待任务的完整业务逻辑执行完成后再释放,相当于信号量的计数和任务的实际运行周期完全没有绑定。 - 循环逻辑是串行启动任务:你在foreach中await每个任务的启动过程,本身也不符合并发执行的预期,所有任务会按顺序启动,但因为信号量被提前释放,最终仍然会瞬间启动全部任务。
为什么你的任务没有被计入SemaphoreSlim的限制范围
SemaphoreSlim的限流逻辑依赖「任务开始前占用计数,任务完全结束后释放计数」的规则,你的代码中计数释放的时机远早于任务业务逻辑结束的时间,上一个任务刚启动就释放了名额,下一个任务立刻就能占用名额,自然不会有任何限流效果。
固定并发数的正确实现方案
第一步:调整任务存储模型
将预先存储的任务从预创建的Task改为Func<Task>类型的异步委托,避免嵌套Task问题:
// 模型属性修改 public Func<Task> Operation { get; set; }
任务创建逻辑同步调整:
Func<Task> operation = async () => { await Task.Run(() => Method(input, sps)); }; // 将operation存入ThreadList对应的模型属性即可
第二步:重写StartAsync限流逻辑
public async Task StartAsync() { using SemaphoreSlim concurrencySemaphore = new SemaphoreSlim(5); // 包装所有任务的限流执行逻辑 var executionTasks = ThreadHandler.ThreadList.Select(async threadTask => { // 运行前先占用信号量 await concurrencySemaphore.WaitAsync(); try { // 等待业务逻辑完全执行完成 await threadTask.Operation(); } finally { // 业务逻辑执行完成后再释放信号量 concurrencySemaphore.Release(); } }); // 等待所有任务全部执行完成 await Task.WhenAll(executionTasks); }
第三步:简化触发逻辑
不需要额外包一层Task.Run调用异步方法,直接await即可:
await mc.StartAsync();
内容的提问来源于stack exchange,提问作者Proclarush
相关产品推荐
相关产品推荐

