Can .NET ThreadPool minThreads Cause Latency Spikes Under Concurrent Requests?
0 reputation · 17 Jan 2022, 22:31 UTC
0 reputation · 17 Jan 2022, 22:31 UTC
Determine whether raising the .NET ThreadPool minimum thread count can prevent request-queueing latency that appears only under concurrent load in an ASP.NET Core application targeting .NET 8.
The default minThreads value is derived from static heuristics and does not adapt to workload characteristics. Under bursty traffic, Kestrel may accept more connections than the ThreadPool can immediately serve, so excess requests wait in the queue. The documented interaction between Kestrel’s per-endpoint connection limit and ThreadPool scaling is incomplete, making it unclear whether adjusting minThreads alone resolves the spike or merely shifts the bottleneck.
Specific questions:
35775 reputation · 18 Jan 2022, 08:42 UTC
Raising ThreadPool.SetMinThreads can reduce p99 latency during bursty concurrent requests by keeping more worker threads warm, but it does not guarantee elimination of queueing latency. The improvement only materializes when the ThreadPool's hill-climbing algorithm is the bottleneck—not when Kestrel's connection limits, CPU saturation, or synchronous blocking elsewhere constrain throughput.
Kestrel enforces a per-endpoint connection limit (default: unbounded for HTTP/1.1, 100 for HTTP/2). When concurrent connections exceed available ThreadPool threads, requests queue in the ThreadPool's global queue. Increasing minThreads pre-allocates threads so fewer requests wait for hill-climbing (which adds ~500 ms per new thread by default). However, if Kestrel's Limits.MaxConcurrentConnections or MaxConcurrentUpgradedConnections is set lower than your minThreads target, the connection cap becomes the effective bottleneck and extra threads sit idle.
Precedence: the lower of (Kestrel connection limit, ThreadPool available threads) governs actual concurrency. Adjust both in tandem.
| Signal | ThreadPool Queueing | GC Pause |
|---|---|---|
| Latency pattern | Sustained elevation across many requests; queue length grows | Brief, periodic spikes aligning with Gen 2 collections |
| Key metric | ThreadPool.QueueLength / ThreadPool.ThreadCount | dotnet-counters GC pause duration, % Time in GC |
| Correlation | Queue length correlates with p99 latency | Spikes correlate with GC events, not queue depth |
wrk -t4 -c200 -d30s).dotnet-counters monitor --process-name YourApp --counters System.Threading.ThreadPool
Watch QueueLength and ThreadCount.dotnet-counters monitor --process-name YourApp --counters System.Runtime.GC
Note Gen 2 GC Count, Pause Duration, % Time in GC.ThreadPool.SetMinThreads(worker: 200, completionPort: 200) at startup (tune to expected peak concurrency).What is your current KestrelServerOptions.Limits.MaxConcurrentConnections value (or the default you're relying on)? If it's unset or lower than your target minThreads, raising the ThreadPool minimum alone will not relieve the queue.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.