Creation and destruction of new threads comes with a cost and it affects an application’s performance. Threads can also be blocked or go into sleep or other unresolved states. If your app doesn’t distribute the workload properly, worker threads may spend the majority of their time sleeping. This is where thread pool comes in handy.
A thread pool is a pool of worker threads that have already been created and are available for apps to use them as needed. Once thread pool threads finish executing their tasks, they go back to the pool.
.NET provides a managed thread pool via the ThreadPool class that is managed by the system. As a developer, we don’t need to deal with the thread management overhead. For any short background tasks, the managed thread pool is a better choice than creating and managing your own threads. Thread pool threads are good for background processes only and not recommended for foreground threads. There is only one thread pool per process.
Use of thread pool is not recommended when,
- You need to prioritize a thread.
- Thread is a foreground thread.
- You have tasks that cause the thread to block for long periods of time. The thread pool has a maximum number of threads, so a large number of blocked thread pool threads might prevent tasks from starting.
- You need to place threads into a single-threaded apartment. All ThreadPool threads are in the multithreaded apartment.
- You need to have a stable identity associated with the thread, or to dedicate a thread to a task.
ThreadPool
The ThreadPool class has several static methods including the QueueUserWorkItem that is responsible for calling a thread pool worker thread when it is available. If no worker thread is available in the thread pool, it waits until the thread becomes available.
The QueueWorkItem method takes a procedure that executes in the background.
ThreadPool.QueueUserWorkItem(BackgroundTask);
Here is a complete example of how to call a worker thread from thread pool to execute a method in the background.
- using System;
- using System.Threading;
-
- class ThreadPoolSample
- {
-
- static void BackgroundTask(Object stateInfo)
- {
- Console.WriteLine("Hello! I'm a worker from ThreadPool");
- Thread.Sleep(1000);
- }
-
- static void Main(string[] args)
- {
-
- ThreadPool.QueueUserWorkItem(BackgroundTask);
- Console.WriteLine("Main thread does some work, then sleeps.");
- Thread.Sleep(500);
- Console.WriteLine("Main thread exits.");
- Console.ReadKey();
- }
- }
You can also pass values to a background method via the QueueWorkItem method. The second parameter of the method is an object that can be any object you would like to pass to your background procedure.
Let’s assume we have a Person class with the following members.
-
- public class Person
- {
- public string Name { get; set; }
- public int Age { get; set; }
- public string Sex { get; set; }
-
- public Person(string name, int age, string sex)
- {
- this.Name = name;
- this.Age = age;
- this.Sex = sex;
- }
- }
We can create an object of Person type and pass it to the QueueUserWorkItem method.
-
- Person p = new Person("Mahesh Chand", 40, "Male");
- ThreadPool.QueueUserWorkItem(BackgroundTaskWithObject, p);
- And in the worker method, we can extract values from the object and use it. In the following example, I read back the Person.Name and displays on the console.
- static void BackgroundTaskWithObject(Object stateInfo)
- {
- Person data = (Person)stateInfo;
- Console.WriteLine($"Hi {data.Name} from ThreadPool.");
- Thread.Sleep(1000);
- }
The complete code is listed in the following code.
- using System;
- using System.Threading;
-
- class ThreadPoolSample
- {
-
- static void BackgroundTask(Object stateInfo)
- {
- Console.WriteLine("Hello! I'm a worker from ThreadPool");
- Thread.Sleep(1000);
- }
-
- static void BackgroundTaskWithObject(Object stateInfo)
- {
- Person data = (Person)stateInfo;
- Console.WriteLine($"Hi {data.Name} from ThreadPool.");
- Thread.Sleep(1000);
- }
- static void Main(string[] args)
- {
-
- Person p = new Person("Mahesh Chand", 40, "Male");
- ThreadPool.QueueUserWorkItem(BackgroundTaskWithObject, p);
-
- Console.ReadKey();
- }
-
-
- public class Person
- {
- public string Name { get; set; }
- public int Age { get; set; }
- public string Sex { get; set; }
-
- public Person(string name, int age, string sex)
- {
- this.Name = name;
- this.Age = age;
- this.Sex = sex;
- }
- }
- }
Maximum and Minimum Thread Pool Threads
Thread pool size is the number of threads available in a thread pool. The thread pool provides new worker threads or I/O completion threads on demand until it reaches the minimum for each category. By default, the minimum number of threads is set to the number of processors on a system. When the minimum is reached, the thread pool can create additional threads in that category or wait until some tasks complete. The thread pool creates and destroys threads to optimize throughput, which is defined as the number of tasks that complete per unit of time. Too few threads might not make optimal use of available resources, whereas too many threads could increase resource contention.
ThreadPool.GetAvailalbeThreads returns the number of threads that are available currently in a pool. It is the number of maximum threads minus currently active threads.
- int workers, ports;
-
- ThreadPool.GetAvailableThreads(out workers, out ports);
- Console.WriteLine($"Availalbe worker threads: {workers} ");
- Console.WriteLine($"Available completion port threads: {ports}");
ThreadPool.GetMaxThreads and ThreadPool.GetMinThreads returns the maximum and minimum threads available in a thread pool.
- int workers, ports;
-
- ThreadPool.GetMaxThreads(out workers, out ports);
- Console.WriteLine($"Maximum worker threads: {workers} ");
- Console.WriteLine($"Maximum completion port threads: {ports}");
ThreadPool.SetMaxThreads and ThreadPool.SetMinThreads are used to set maximum and minimum number of threads on demand as needed in a thread pool. By default, the minimum number of threads is set to the number of processors on a system.
- int workers, ports;
-
- int minWorker, minIOC;
- ThreadPool.GetMinThreads(out minWorker, out minIOC);
- ThreadPool.SetMinThreads(4, minIOC);
The complete example is the following code.
- using System;
- using System.Threading;
-
- class ThreadPoolSample
- {
-
- static void BackgroundTask(Object stateInfo)
- {
- Console.WriteLine("Hello! I'm a worker from ThreadPool");
- Thread.Sleep(1000);
- }
-
- static void BackgroundTaskWithObject(Object stateInfo)
- {
- Person data = (Person)stateInfo;
- Console.WriteLine($"Hi {data.Name} from ThreadPool.");
- Thread.Sleep(1000);
- }
- static void Main(string[] args)
- {
-
- ThreadPool.QueueUserWorkItem(BackgroundTask);
- Console.WriteLine("Main thread does some work, then sleeps.");
- Thread.Sleep(500);
-
-
- Person p = new Person("Mahesh Chand", 40, "Male");
- ThreadPool.QueueUserWorkItem(BackgroundTaskWithObject, p);
-
- int workers, ports;
-
-
- ThreadPool.GetMaxThreads(out workers, out ports);
- Console.WriteLine($"Maximum worker threads: {workers} ");
- Console.WriteLine($"Maximum completion port threads: {ports}");
-
-
- ThreadPool.GetAvailableThreads(out workers, out ports);
- Console.WriteLine($"Availalbe worker threads: {workers} ");
- Console.WriteLine($"Available completion port threads: {ports}");
-
-
- int minWorker, minIOC;
- ThreadPool.GetMinThreads(out minWorker, out minIOC);
- ThreadPool.SetMinThreads(4, minIOC);
-
-
- int processCount = Environment.ProcessorCount;
- Console.WriteLine($"No. of processes available on the system: {processCount}");
-
-
- ThreadPool.GetMinThreads(out workers, out ports);
- Console.WriteLine($"Minimum worker threads: {workers} ");
- Console.WriteLine($"Minimum completion port threads: {ports}");
-
- Console.ReadKey();
- }
-
-
- public class Person
- {
- public string Name { get; set; }
- public int Age { get; set; }
- public string Sex { get; set; }
-
- public Person(string name, int age, string sex)
- {
- this.Name = name;
- this.Age = age;
- this.Sex = sex;
- }
- }
- }
Summary
In this article with the code sample, we learned what a thread pool is and how to use it in .NET Core apps.