C#中Polly悲观超时策略多线程执行变慢问题咨询
问题分析:Polly悲观超时导致任务执行骤降并抛出超时异常
先看你的测试代码:
using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Polly; using Polly.Timeout; namespace Test { public class Program { public static async Task Main() { var tasks = new List<Task>(); for (var i = 0; i < 20; i++) { var task = new Task(Test); tasks.Add(task); } foreach (var task in tasks) task.Start(); await Task.WhenAll(tasks); } public static void Test() { Console.WriteLine($"Executing Test() at {DateTime.Now}"); Policy.Timeout(TimeSpan.FromSeconds(2), TimeoutStrategy.Pessimistic) .Execute(() => { Thread.Sleep(200); }); } } }
执行输出:
Executing Test() at 2018-05-16 15:10:21 Executing Test() at 2018-05-16 15:10:21 Executing Test() at 2018-05-16 15:10:21 Executing Test() at 2018-05-16 15:10:21 Executing Test() at 2018-05-16 15:10:21 Executing Test() at 2018-05-16 15:10:21 Executing Test() at 2018-05-16 15:10:21 Executing Test() at 2018-05-16 15:10:21 Executing Test() at 2018-05-16 15:10:22 Executing Test() at 2018-05-16 15:10:23 Executing Test() at 2018-05-16 15:10:24 Executing Test() at 2018-05-16 15:10:24 Executing Test() at 2018-05-16 15:10:24 Executing Test() at 2018-05-16 15:10:25 Executing Test() at 2018-05-16 15:10:26 Executing Test() at 2018-05-16 15:10:26 Executing Test() at 2018-05-16 15:10:27 Executing Test() at 2018-05-16 15:10:27 Executing Test() at 2018-05-16 15:10:27 Executing Test() at 2018-05-16 15:10:28
最终抛出Polly.Timeout.TimeoutRejectedException,核心原因和你用的Polly 6.0.1版本的悲观超时实现,以及.NET线程池的默认配置直接相关:
核心问题:悲观超时的线程资源消耗
Polly 6.x版本的悲观超时策略(Pessimistic),是通过「额外线程监控超时」来实现的:它会把你的业务逻辑(也就是Thread.Sleep(200))放到一个单独的线程池线程执行,同时再启动另一个线程来监控超时时间——一旦到达超时阈值就终止执行业务的线程。
而.NET线程池默认的最小工作线程数是8,你一次性启动20个任务,每个任务要占用2个线程(一个执行业务,一个监控超时),8个初始线程瞬间就被占满了。
线程池在耗尽初始线程后,不会立刻批量创建新线程,而是有个「冷却机制」——默认每500ms才会新增一个线程。所以后续的任务只能排队等待线程分配,这就是你看到输出里时间间隔越来越大的原因。更糟的是,有些任务等待线程的时间就超过了你设置的2秒超时,直接触发了TimeoutRejectedException。
为什么乐观超时或无超时就正常?
- 无超时策略:每个任务只占用1个线程池线程,你的业务逻辑仅休眠200ms,线程很快就能释放,线程池扩容压力极小,自然不会出现卡顿。
- 乐观超时策略:它基于
CancellationToken实现,不需要额外的监控线程——只是在业务逻辑中检查令牌是否超时,全程仅占用1个线程,不会浪费线程资源,因此不会出现线程池耗尽的问题。
针对你的场景的解决思路
如果业务必须使用悲观超时,在Polly 6.x版本中可以临时通过ThreadPool.SetMinThreads()调高线程池最小工作线程数来缓解,但这属于治标不治本的办法。更推荐的方案是升级Polly到7.x及以上版本——后续版本对悲观超时的实现做了优化,大幅减少了线程资源的消耗,从根源上解决这个问题。
内容的提问来源于stack exchange,提问作者picusiaubas
相关产品推荐
相关产品推荐

