为什么C#中Task.Run搭配Task.WaitAll会出现线程阻塞问题
问题结论
该场景不属于死锁,是线程池饥饿,属于同步阻塞异步任务的典型异常表现,背后运行逻辑如下:
运行原理拆解
初始8个线程的来源
.NET 线程池默认的最小工作线程数和CPU逻辑核心数一致,你日志里首次启动就有8个活跃工作线程,说明你的设备是8逻辑核,这是线程池默认的最小线程阈值,线程池会优先维持这个数量的线程可用。任务阻塞的核心原因
你通过Task.Run把100个任务全部投递到线程池工作队列,每个任务执行时都会调用Task.WaitAll同步阻塞当前线程,等待Task.Delay生成的任务完成。
而Task.Delay的到期回调默认会投递到线程池的工作队列执行,此时所有可用的工作线程都被你投递的100个任务占满,全部处于阻塞状态,没有空闲线程处理Task.Delay的回调,因此t1、t2永远不会被标记为完成状态,Task.WaitAll就会一直阻塞不会返回。线程每秒新增的逻辑
当线程池检测到工作队列有排队任务、且当前所有工作线程都处于忙碌/阻塞状态时,会按照每秒1个的速度新增工作线程来消化队列任务。但你新增的线程一旦启动,就会执行到Task.WaitAll进入阻塞状态,还是没有多余线程处理Task.Delay的回调,最终就会出现线程数持续上涨但所有任务永远卡住的现象。
补充验证
你可以在代码开头添加ThreadPool.SetMinThreads(100, 100)修改线程池最小线程数,此时线程池会一次性初始化100个工作线程,处理完你投递的100个阻塞任务后还有剩余线程处理Task.Delay的回调,所有任务都会在5秒后正常执行结束。
内容的提问来源于stack exchange,提问作者Shiglet

