Blog Post

IIS Support Blog
4 MIN READ

Modifying the .NET CLR ThreadPool Settings for ASP.NET 4.x

MattHamrick's avatar
MattHamrick
Icon for Microsoft rankMicrosoft
Apr 17, 2019

One common problem we see is the blocking of .NET ThreadPool threads. This blocking can lead to ThreadPool thread starvation, and sometimes even deadlocks.

This is especially true as of late with more and more folks using asynchronous code, and trying to convert existing, synchronous methods into async, but leaving parts of the code sync.

I don't go into the above scenarios in this post; but, some folks want to change the ThreadPool defaults regarding the number of maximum or minimum worker and/or IO threads to troubleshoot a problem or test application behavior with different settings. There is conflicting information out there on how to change the ThreadPool settings for an ASP.NET 4.x web app, so the purpose of this post is to cover how to make those changes correctly.

 

NOTE: We generally don't recommend modifying the CLR TP thread counts, as the defaults work for the vast majority of scenarios.

 

If you want to change the ThreadPool settings in an ASP.NET application, you have three (3) choices:

  1. Modify the system.web\processModel element in the machine.config. There could be several copies of this file on a typical server. For the most part, these are the ones we deal with:
    • C:\Windows\Microsoft.NET\Framework[64]\v2.0.50727\CONFIG\machine.config
    • C:\Windows\Microsoft.NET\Framework[64]\v4.0.30319\CONFIG\machine.config
  2. Use ThreadPool.SetMaxThreads and ThreadPool.SetMinThreads from within the application itself
  3. If using IIS 10 (Windows Server 2016 and up), then you can set an environment variable specific to the application pool that is hosting the application. 

1 - machine.config

For #1, here's how the processModel section is declared in the machine.config:

            <section name="processModel" type="System.Web.Configuration.ProcessModelSection, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" allowDefinition="MachineOnly" allowLocation="false"/>

 

allowDefinition="MachineOnly" means this section/element can only be configured in the machine.config, and not any other downlevel config. The allowLocation="false" means this section cannot be placed in a <location> tag.

This means if you try to change the processModel element in an app's web.config or try to modify a copy of one inside a location tag, you'll receive an error. 

 

A modifed processModel element could look like this:
<processModel autoConfig="false" maxWorkerThreads="100" minWorkerThreads="2" maxIoThreads="100" minIoThreads="2" />

 

For any explicit thread counts to be honored, you'd need to set autoConfig="false" - by default it's set to true.

 

Some folks have tried placing a modified processModel element into a separate .config file, then configuring the IIS host's application pool's CLRConfigFile property to load that file. (This feature is described here.) This does not work for modifying the .NET ThreadPool, and will not modify the actual <processModel> settings that get pulled-in.

This also means modifying the Aspnet.config file that exists in the Framework[64]\[version]\ directory to add a system.web\processModel element will not work. We are limited to changing this element in the machine.config only.

But wait, doesn't editing the machine.config affect the entire server? Yes, it does! Granted, we're only editing the system.web section, so only apps that consume that section will be modified. Still, this is a reason to not make the change here, even though you can.

 

2 - Code

You can make this change anywhere in the app (even on an aspx page or an MVC View, for example) and it will stick for the rest of the app's lifetime, or until it gets changed again. When the application pool/domain/etc. is recycled, it will pull the defaults until your call to modify the TP is made again.

One idea is to use the app's *.config (or wherever you're storing app settings) to store your desired changes, then pull those values into the app and apply them early-on in the application's lifetime, such as during application startup. 

 

3 - IIS 10 Environment Variable

The environment variable that will set the minWorkerThreads for .NET Framework is this: COMPlus_ThreadPool_ForceMinWorkerThreads

This can be set on the machine as a whole, but IIS 10 gained the ability to set environment variables per application pool. This will affect every application hosted in the pool, but it's better than affecting every application on the machine.

There are a few different ways to set app pool environment variables in IIS, so here are two of them:

  1. via IIS Manager's Configuration Editor, or
  2. via the appcmd.exe

Here's the docs page for the configuration element:

https://learn.microsoft.com/en-us/iis/configuration/system.applicationHost/applicationPools/add/environmentVariables/

 

Note: when setting this environment variable, the value is read by .NET as hexadecimal

 

In IIS Manager, navigate to the server/root node, then:

Configuration Editor -> Section: system.applicationHost/applicationPools -> Click the (Collection) field and click the little "..." button that appears -> select the desired app pool in the list -> select "environmentVariables" and open that collection via the "..." button:

Name: COMPlus_ThreadPool_ForceMinWorkerThreads

Value: ##

 

When using appcmd.exe, here are some example commands - note you would need to change the indicated portions:

To add the env var if it does not exist:
C:\windows\system32\inetsrv\appcmd.exe set config -section:system.applicationHost/applicationPools /+"[name='DefaultAppPool'].environmentVariables.[name='COMPlus_ThreadPool_ForceMinWorkerThreads',value='64']" /commit:apphost

 

To modify the env var if it does exist:
C:\windows\system32\inetsrv\appcmd.exe set config -section:system.applicationHost/applicationPools /[name='DefaultAppPool'].environmentVariables.[name='COMPlus_ThreadPool_ForceMinWorkerThreads'].value:"96" /commit:apphost

 

To remove the env var:
C:\windows\system32\inetsrv\appcmd.exe set config -section:system.applicationHost/applicationPools /-"[name='DefaultAppPool'].environmentVariables.[name='COMPlus_ThreadPool_ForceMinWorkerThreads']" /commit:apphost

 

 

Again, we don't recommend modifying the ThreadPool counts unless absolutely necessary. This post was just to show how to make the changes so they stick.

Updated Aug 09, 2023
Version 2.0

6 Comments

  • ironfly's avatar
    ironfly
    Copper Contributor

    thanks MattHamrick , that did the trick!  had another app in the service plan, i added the min threadpool to its global.asax and the issue is resolved!

  • ironfly the only time ASP.NET itself modifies the ThreadPool settings is during webapp startup. Thus, if you are observing the max/min counts changing afterwards, it either means your app is restarting or another app in the same worker process is being started (the default .NET ThreadPool is shared amongst all apps in a worker process), or your own code is changing it somewhere. Since you're running an Azure App Service, perhaps you have multiple webapps in the same App Service Plan? If so, then if any of those separate apps will reset the ThreadPool max/min counts when starting, and the counts will remain that way unless they are modified by custom code again. 

  • ironfly's avatar
    ironfly
    Copper Contributor

    We are having this exact same problem, we are setting the ThreadPool.SetMinThread in our Global.ascx and it shows our new ThreadPool.SetMinThread for a few minutes then reverts back to 4 (we are setting to 100).  this is an azure webapp website (asp.net).

     

    We are not having this issue on our dev server (basic IIS 10 box).

     

    Is there a fix for this?  A setting in teh application settings in azure or something?

     

    MattHamrick could you provide the answer/solution for us as well?  

     

    Thanks, kevin

  • TomPester's avatar
    TomPester
    Copper Contributor

    Hi Matt,

     

    We see that the minium worker threads are reset to the default value after a while without known reason

     

    I read your blog post below during my research on how the threadpool works.

     

    We are forced to call the ThreadPool.SetMinThread(x,y) method where we set the x, the minimum number of workter threads, to a high enough number.

     

    However, and this is causing my hair to turn gray, after some time it is rest back to the default which is 1 thread per logical processor right.

    What is causing this minimum to reset? The appdomain and application pool aren't recylced as far as I can tell.

    Sometimes it happens after a few minutes and sometimes a few hours.

     

    Is there a process/mechanism that resets the threadpool minimum number of threads that you know of?

    The only trace on the internet that mentions this problem is at stackoverflow
    https://stackoverflow.com/questions/14821874/why-is-threadpool-setminthreads-not-changing-the-minimum-value

     

    We call the ThreadPool.SetMinThread method in the Application_Start of a web application.

     

    I hope you can offer a clue as to what is going on because I have been hunting this for already more than a week and I still cannot explain it

     

    Kind Regards, Tom

     

    Modifying the .NET CLR ThreadPool Settings for ASP.NET 4.x
    https://techcommunity.microsoft.com/t5/iis-support-blog/modifying-the-net-clr-threadpool-settings-for-asp-net-4-x/ba-p/357985