[Note: This has been fixed in .NET 3.5 SP1, read more on this post.]
Holy smokes! I thought we had figured out something significant when I posted .NET 3.5 Brings Major (Undocumented) Changes to ThreadPool where we discovered that the .NET 3.5 ThreadPool changed the allocation algorithm for adding threads from linear to logarithmic.
This is bigger. [Recently updated see note below]
Here’s the scenario. I have a server – say in the financial sector – that must process many requests and it must get up to speed immediately. I can’t pay the 500 ms warm up time for the ThreadPool (.NET 2.0) or the even slower model in .NET 2.0 SP1. What do I do? I call ThreadPool.SetMinThreads(x, x) where x < (the current max), but much higher than 2 (the default). So I might call ThreadPool.SetMinThreads(100, 100) or something like that.
In .NET 1.0 – .NET 2.0 (without SP1) you would go from the warm up time model:
warm up time model (.NET 2.0)
To this immediate “ready” threading model.
ready model (.NET 2.0)
Notice that we immediately have 100 threads available in this case. BUT, and here’s the but: .NET 3.5 (read .NET 2.0 SP1) *ignores* SetMinThreads. Oh, it claims to respect SetMinThreads:
Running on 2.0.50727.1433
Max Currently: 500, 1000
Min Currently: 2, 2
Set min thread count 100 (y/n)? y
Setting min to 100
Min Currently: 100, 100
But, look at the thread graph that results!
ready model (.NET 2.0 SP1 — i.e. .NET 3.5)
I don’t know about you, but that doesn’t look any different AT ALL than if we do not call SetMinThreads:
warm up time model (.NET 2.0 SP1 — i.e. .NET 3.5)
What do we conclude from this? .NET 2.0 SP1 does not respect ThreadPool.SetMinThreads. To me, that’s a big breaking change. My financial app just stopped working. Yikes! So what happened to my thread pool and SetMinThreads in .NET 2.0 SP1? Anyone?
Don’t take my word for it. Run this program on both .NET 2.0 (SP0) and .NET 2.0 SP1 and you’ll see for yourself.
Here’s the EXE:
And the source code is below:
using System; using System.Threading; namespace NewThreadPoolBehavior { internal class Program { private static void Main(string[] args) { Console.WriteLine( "Running on " + Environment.Version ); int w, c; ThreadPool.GetMaxThreads( out w, out c ); Console.WriteLine( "Max Currently: " + w + ", " + c ); ThreadPool.GetMinThreads( out w, out c ); Console.WriteLine( "Min Currently: " + w + ", " + c ); Console.WriteLine( "Set min thread count 100? (y/n) " ); string txt = Console.ReadLine(); if ( txt == "y" ) { Console.WriteLine( "Setting min to 100" ); ThreadPool.SetMinThreads( 100, 100 ); ThreadPool.GetMinThreads( out w, out c ); Console.WriteLine( "Min Currently: " + w + ", " + c ); } UseThreadPool( 200 ); Console.ReadLine(); } private static void UseThreadPool(int count) { for ( int i = 0; i < count; i++ ) { ThreadPool.QueueUserWorkItem( delegate { SlowMethod(); } ); } } private static int concurrent = 0; private static void SlowMethod() { concurrent++; Console.WriteLine( "Starting ops (" + concurrent + " concurrent)" ); Thread.Sleep( 20000 ); Console.WriteLine( "Finished ops (" + concurrent + " concurrent)" ); concurrent--; } } }
[Update: 2/25/2008 – You may be thinking so what’s the big deal? Most
applications don’t directly program against the ThreadPool so this is
just some edge case. To make this more real for everyone, this applies
to you if you use any of the following: ASP.NET, WCF, .NET Remoting,
Delegate.BeginInvoke, SqlCommand.BeginExecute*, Windows Workflow, and more. Sounds serious now right?]
[Update: 5/12/2008 – Please see my lastest followup on this topic: More on the ThreadPool Bug in .NET 2.0 SP1.]