Course contentsShow
← C#
Lesson 1761 of 1,90816. Performance and DiagnosticsFree lesson

Diagnosing Thread Pool Starvation Issues

Learners will monitor thread injection rates to detect thread starvation before it impacts latency.

In this lesson, you will learn how to proactively monitor thread injection rates to detect and mitigate thread pool starvation before it triggers latency spikes in your application.

The Thread Injection Mechanism

The .NET Thread Pool is designed to grow dynamically. When it detects that work items are queuing up, it "injects" new threads to help process the backlog. However, this injection is intentionally throttled—typically at a rate of one thread every 500 milliseconds—to prevent the CPU from thrashing during transient spikes.

Starvation occurs when your application demands new threads faster than the pool can provide them, or when existing threads are blocked by synchronous I/O or long-running, CPU-bound tasks. By the time your end-users notice a lag, the pool is often already critically starved.

Monitoring via EventCounters

To diagnose this, we look at ThreadPoolWorkerThreadAdjustmentReason. When this value is Starvation, the runtime has identified that it needs more threads but cannot provide them quickly enough. You can track this using the System.Runtime EventSource:

// Logic to hook into EventCounters
// Monitor "threadpool-completed-items" vs "threadpool-queue-length"
// If queue length grows while completed items stagnate,
// check for ThreadPoolWorkerThreadAdjustmentReason.

If you see the threadpool-queue-length climbing consistently while threadpool-thread-count remains near its limit, your application is experiencing starvation.

Detecting the Bottleneck

The most common culprit is "sync-over-async" code. If a thread enters a Task.Wait() or .Result call while holding a resource needed by the very task it is waiting for, it creates a circular dependency. The thread pool sees the blocked thread, attempts to inject a new one, hits the 500ms injection limit, and effectively halts progress.

To prevent this, ensure your asynchronous call chains are fully awaited. Use diagnostic tools to correlate ThreadPoolWorkerThreadAdjustmentReason events with your request latency metrics. If you see high Starvation counts preceding a latency spike, you have identified your performance bottleneck before it became a total system outage.

Key Takeaway: Monitor the thread injection rate and queue length via EventCounters to identify starvation early, rather than waiting for latency metrics to signal that the system has already collapsed.