Skip to main content

How to Raise the Minimum Threads of the .NET Thread Pool

· 5 min read
Jagdish Kumawat
Founder @ Dewiride

Up to its minimum, the .NET thread pool creates threads as soon as they're needed. Past that, it adds a couple per second. So raising the minimum helps a burst of blocking work. It stopped a real crash on my API, and I measured what it costs.

Three Ways to Set It​

All three do the same thing. Microsoft's threading configuration page lists them.

In the project file, which is the one I'd use, because it travels with the code:

Dewiride.Inventory.Api.csproj
<PropertyGroup>
<ThreadPoolMinThreads>500</ThreadPoolMinThreads>
</PropertyGroup>

The build writes it into runtimeconfig.json, so you can check it there:

bin/Debug/net10.0/Dewiride.Inventory.Api.runtimeconfig.json
"System.Threading.ThreadPool.MinThreads": 500

As an environment variable, for trying it without a rebuild. The value is hexadecimal, so 500 is 1F4:

Terminal
DOTNET_ThreadPool_ForceMinWorkerThreads=1F4 dotnet run --project src/Dewiride.Inventory.Api

In PowerShell, use $env:DOTNET_ThreadPool_ForceMinWorkerThreads = "1F4" on the line before.

In code, at the start of Program.cs:

Program.cs
ThreadPool.GetMinThreads(out var before, out var ioThreads);
Console.WriteLine($"Before: {before} worker threads");

ThreadPool.SetMinThreads(500, ioThreads);

ThreadPool.GetMinThreads(out var after, out _);
Console.WriteLine($"After: {after} worker threads");
Output
Before: 10 worker threads
After: 500 worker threads

By default, the minimum is the number of processors, which is 10 on this computer.

What It Did for Me​

A .NET API using MySql.Data blocks a thread while it waits for a pooled connection. With 150 requests at once and a pool of 100, the thread pool starved, and the process often ended after about 15 seconds. The same burst of 600 requests, repeated with each setting:

SettingRunsResult
The default26The process ended in 17, and 4 more gave errors. Only 5 were clean
DOTNET_ThreadPool_ForceMinWorkerThreads=1F48All 600 answered 200 every time, in about 2.4 s
ThreadPoolMinThreads of 5003All 600 answered 200 every time, in about 2.5 s

With the default, a run in which 587 requests got no answer. With a minimum of 500 threads, all 600 answering 200.

It held at 800 at once as well: all 3,200 answered.

What It Costs​

Each blocked thread is a real thread. I compared it with a concurrency limit in front of the pool, which queues extra requests without holding any thread, at 500 requests at once and a pool of 25:

Time for 2,000 requestsThreads in the process afterwards
A minimum of 500 threads6.2 s386 to 425
A concurrency limit6.0 s38 to 39

Same speed, with ten times the threads. It also doesn't limit the wait: past the pool, a request can still block for up to Connect Timeout, and then fail. So it's a real fix for a crash, and it's the quickest one to try. But where you can keep requests from blocking in the first place, do that. A higher minimum is a good second layer if some blocking is out of your hands.

Choosing a Number​

Set it to about the most requests you expect to block at the same moment, and no higher. Microsoft's documentation warns: "Using the SetMinThreads method to increase the minimum number of threads can cause performance problems as described in the preceding text. In most cases, the thread pool will perform better with its own algorithm for allocating threads."

Common Mistakes​

MistakeWhat happensDo this instead
Writing 500 in the environment variableIt's read as hexadecimal: 1,280 threadsWrite 1F4
Raising it instead of finding what blocksMemory goes up, and the blocking staysFind it first
Setting it huge "to be safe"Threads compete for CPU timeSize it to your load

Troubleshooting​

SymptomLikely causeFix
No changeThe setting isn't in runtimeconfig.jsonRebuild, and check the file
Memory went upOne thread's stack per threadLower it
Still slowThe blocking is longer than the burstLimit requests

Conclusion​

Set ThreadPoolMinThreads in the project file, and threads are there when a burst arrives. It stopped a crash here, at the cost of hundreds of threads, so prefer not blocking if you can.

Additional Resources​

Stay Updated

Subscribe to our newsletter for the latest tutorials, tech insights, and developer news.

By subscribing, you agree to our privacy policy. Unsubscribe at any time.