C# Multithreading Journey (3) – Using the Thread Pool
This article explains how C# thread pools reduce thread‑creation overhead, describes several ways to enqueue work (Task Parallel Library, ThreadPool.QueueUserWorkItem, asynchronous delegates, BackgroundWorker), provides practical code examples, and outlines important considerations such as thread naming, priority, and exception handling.
Thread pools in .NET reuse and recycle threads to avoid the cost of creating a new stack (about 1 MB) for each thread, limit the total number of concurrent threads, and improve performance on multi‑core processors.
Ways to enqueue work to the thread pool:
Task Parallel Library (TPL) – use Task or Task<TResult>.
Direct call to ThreadPool.QueueUserWorkItem.
Asynchronous delegates (BeginInvoke/EndInvoke).
BackgroundWorker.
Typical scenarios that already use the pool include WCF/Remoting/ASP.NET services, System.Timers.Timer, System.Threading.Timer, framework async methods (e.g., WebClient), and PLINQ.
Using TPL: create a task with Task.Factory.StartNew and wait for it with task.Wait(). Example:
static void Main(string[] args)
{
Task task = Task.Factory.StartNew(Go);
task.Wait();
Console.ReadKey();
}
static void Go()
{
Console.WriteLine("From the thread pool start...");
Thread.Sleep(3000);
Console.WriteLine("From the thread pool end");
}Getting a result with Task<TResult> : the task returns a value via its Result property.
static void Main(string[] args)
{
Task<string> task = Task.Factory.StartNew<string>(() => DownloadString("http://www.baidu.com"));
string result = task.Result; // blocks until the download finishes
}
static string DownloadString(string uri)
{
using (var wc = new System.Net.WebClient())
{
return wc.DownloadString(uri);
}
}Using ThreadPool.QueueUserWorkItem : enqueue a method that receives an object argument. No return value is provided, so you must handle exceptions inside the method.
static void Main(string[] args)
{
ThreadPool.QueueUserWorkItem(Go);
ThreadPool.QueueUserWorkItem(Go, 123);
Console.ReadKey();
}
static void Go(object data)
{
Console.WriteLine("A from thread pool! " + data);
}Using asynchronous delegates: create a delegate, call BeginInvoke to start it, and later call EndInvoke to obtain the return value and re‑throw any unhandled exception.
static void Main(string[] args)
{
Func<string, int> t = Go;
IAsyncResult result = t.BeginInvoke("test", null, null);
// other work can be done here
int length = t.EndInvoke(result);
Console.WriteLine("String length is: " + length);
Console.ReadKey();
}
static int Go(string message)
{
return message.Length;
}Important considerations when using the thread pool:
You cannot set a thread name (makes debugging harder).
All pool threads are background threads.
Calling ThreadPool.SetMinThreads can reduce start‑up latency.
Thread priority cannot be changed permanently; it is reset when the thread returns to the pool.
Use Thread.CurrentThread.IsThreadPoolThread to check if the current thread comes from the pool.
The article also notes that the author hopes readers find the guide helpful and encourages support from the community.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Wukong Talks Architecture
Explaining distributed systems and architecture through stories. Author of the "JVM Performance Tuning in Practice" column, open-source author of "Spring Cloud in Practice PassJava", and independently developed a PMP practice quiz mini-program.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
