Applications developed today do not make use of the System Thread class.Threading straight. It means you are still keeping legacy code if you see it in one of your projects. Is learning Thread API worthwhile? Well, that's a different subject altogether. The Thread API was introduced in.NET 1.0, as we well know, but it was not very user-friendly. Why then is it preferable not to utilize it?

  • The underlying API that gives you direct control over everything is called Thread API. Although this choice seems appealing, you can become frustrated if you have more influence over something you don't understand.
  • Gaining mastery over Thread API was not an easy feat. It has a rich history that you must really understand by delving into the OS's intricacies. It does seem a little frightening, but the Thread API makes you think in terms of Threads rather than jobs. Instead of focusing about building threads, developers should consider writing concurrent programming. Thread is an implementation detail and a physical concept.
  • The process of creating a thread is costly.
  • From the standpoint of understandability and maintainability, using Thread API introduces more complexity.

Microsoft introduced the ThreadPool class in.NET 2.0, which is an improvement over the Thread API. Isolating us from the Thread notion was the main advancement toward Threading. It is not necessary for you to consider threads. As a developer, you ought to consider tasks that need to be parallelized.

A wrapper around your threads is called ThreadPool.

Why is it a superior choice?

  • You are no longer "the owner" of Threads thanks to ThreadPool. There is no need for manual development, intricate procedures, thread management issues, deadlock, etc. What do you think? You were the main issue, not a thread:)
  • When and why to create threads are managed by ThreadPool. Put simply, let's say we have five approaches. You should use multithreading to run them. To run them, you require five methods, but ThreadPool might need to use two or three threads to finish your job. If you use a traditional thread API, the limit is determined by the thread count you utilize.
internal class Program
{
    static void Main(string[] args)
    {
        new Thread(ComplexTask).Start();
        Thread.Sleep(1000);
        new Thread(ComplexTask).Start();
        Thread.Sleep(1000);
        new Thread(ComplexTask).Start();
        Thread.Sleep(1000);
        new Thread(ComplexTask).Start();
        Thread.Sleep(1000);
        Console.ReadLine();
    }

    static void ComplexTask()
    {
        Console.WriteLine($"Running complex task in Thread={Environment.CurrentManagedThreadId}");
        //Thread.Sleep(40);
        Console.WriteLine($"Finishing complex task in Thread={Environment.CurrentManagedThreadId}");
    }
}


Here is the result

Every operation is a Thread, and it is not an optimized way of using it.

ThreadPool isolates us from The Thread stuff and provides a simple API to enter multithreading mode. Let’s modify our code and try to understand the value of using ThreadPool instead of Thread directly.
internal class Program
{
    static void Main(string[] args)
    {
        ThreadPool.QueueUserWorkItem((x) => ComplexTask());
        Thread.Sleep(1000);
        ThreadPool.QueueUserWorkItem((x) => ComplexTask());
        Thread.Sleep(1000);
        ThreadPool.QueueUserWorkItem((x) => ComplexTask());
        Thread.Sleep(1000);
        ThreadPool.QueueUserWorkItem((x) => ComplexTask());
        Thread.Sleep(1000);

        Console.ReadLine();
    }
    static void ComplexTask()
    {
        Console.WriteLine($"Running complex task in Thread={Environment.CurrentManagedThreadId}");
        //Thread.Sleep(40);
        Console.WriteLine($"Finishing complex task in Thread={Environment.CurrentManagedThreadId}");
    }
}

As you have probably noticed, ThreadPool optimizes the use of Thread. ThreadPool will utilize an existing thread rather than start a new one if any of them are free. Hold on a minute... Can we perform the same procedure together? Can we really execute the same Thread twice? Let's give it a shot.

You can’t run the same Thread twice but somehow ThreadPool can. ThreadPool can reuse Threads and it helps not to create a Thread every time when you have a block of code to run in multithreading mode. In classical .NET, depending on its version and your CPU architecture the capacity of Worker threads is different.

I have an x64 Intel processor running the most recent version of.NET (.NET 8) with 32767 worker threads with 1000 completion post threads. Typically, ThreadPool begins with just one thread in it. It may automatically increase Threads based on the circumstances. ThreadPool allows you to check the available threads.

In my case, I’m using the latest .NET ( .NET 8) with an x64 intel processor, and I have 32767 worker Threads with 1000 completion post Threads.

ThreadPool usually starts with 1 Thread in the pool. Depending on the context, it may automatically increase Threads. You can Check available threads in ThreadPool.
internal class Program
{
    static void Main(string[] args)
    {
        ThreadCalculator();
        for (int i = 0; i < 10; i++)
        {
            ThreadPool.QueueUserWorkItem((x) => ComplexTask());
        }
        Thread.Sleep(15);
        ThreadCalculator();
        Console.ReadLine();
    }
    static void ThreadCalculator()
    {
        ThreadPool.GetAvailableThreads(out int workerThreads, out int completionPortThreads);
        Console.WriteLine($"worker threads = {workerThreads}, " +
            $"and completion Port Threads = {completionPortThreads}");
    }
    static void ComplexTask()
    {
        Console.WriteLine($"Running complex task in Thread={Environment.CurrentManagedThreadId}");
        //Thread.Sleep(40);
        Console.WriteLine($"Finishing complex task in Thread={Environment.CurrentManagedThreadId}");
    }
}

The most used API in ThreadPool is, of course, QueueUserWorkItem. It has a generic version, also.
Well, we have another great concept called completion post Threads that we need to cover.

ThreadPool.QueueUserWorkItem is a method in C# that allows you to schedule tasks for execution in a thread pool. Here's a breakdown of its functionality:

Purpose

Schedule tasks to run asynchronously without creating and managing individual threads.
Leverages a pool of pre-created threads, improving performance and resource management.

How it works
You provide a delegate (like Action or WaitCallback) that represents the work you want to be done.
Optionally, you can pass an object containing data to be used by the delegate.

ThreadPool.QueueUserWorkItem adds the delegate and data (if provided) to the thread pool's queue.

When a thread from the pool becomes available, it picks up the first item from the queue and executes the delegate with the provided data.

Benefits or Why to Use It

Performance Boost: Reusing threads avoids the overhead of creating and destroying them for each task.
Resource Optimization: Maintains a controlled number of threads, preventing system overload.
Simplified Concurrency Management: The thread pool handles scheduling and ensures tasks are executed efficiently.

Additional notes
The thread pool can dynamically adjust the number of threads based on workload.
Tasks are queued if all threads are busy, ensuring none are dropped.

ThreadPool.QueueUserWorkItem has overloads, including a generic version for type safety and an option to influence thread selection.

Ok, but what about completion port Threads?
The concept of completionPortThreads within the ThreadPool class in C# might not be directly exposed as you might expect.
The ThreadPool class internally manages two types of threads.

  • Worker Threads: These handle general-purpose tasks submitted through QueueUserWorkItem.
  • I/O Completion Port Threads (Completion Port Threads): These are specialized threads optimized for processing asynchronous I/O operations.

Purpose of completion port threads
Asynchronous I/O operations typically involve waiting for network requests, file access, or other external events. Completion port threads efficiently wait for these events using a system mechanism called I/O Completion Ports (IOCP).

When an I/O operation completes, the corresponding completion port thread is notified, and it can then dequeue and process the completed task.

What is the value?

  • Improved performance for asynchronous I/O bound tasks.
  • Completion port threads avoid busy waiting, reducing CPU usage while waiting for I/O events.
  • Dedicated threads for I/O operations prevent worker threads from being blocked, improving overall responsiveness.

Important Notes
You don't directly control completionPortThreads. The ThreadPool class manages its number dynamically based on the system workload. Methods like ThreadPool.SetMinThreads and ThreadPool.GetAvailableThreads allow you to indirectly influence the minimum number of worker and completion port threads, but there's no separate control for each type.

Who Uses the ThreadPool in C#?
The ThreadPool is a fundamental mechanism in C# for efficiently managing threads. It provides a pool of worker threads that can be reused by various parts of your application, including:

  • Task Parallel Library (TPL): When you create Task or Task<TResult> objects, the TPL typically schedules them to run on ThreadPool threads by default. This enables parallel execution of tasks without the need to explicitly manage threads.
  • Asynchronous Programming: Asynchronous operations like those using the async and await keywords often rely on the ThreadPool to execute the actual work in the background while the main thread remains responsive.
  • Parallel Programming: Libraries like the Parallel For loop (Parallel.For) and PLINQ (Parallel LINQ) often leverage the ThreadPool to distribute work items across multiple threads.

Benefits of Using the ThreadPool

  • Reduced Thread Creation Overhead: Creating threads can be expensive. The ThreadPool eliminates this overhead by creating a pool of threads upfront and reusing them as needed.
  • Improved Performance: By efficiently managing threads, the ThreadPool can enhance the responsiveness and throughput of your application, especially when dealing with concurrent tasks.
  • Simplified Thread Management: The ThreadPool handles thread creation, destruction, and idle thread management, freeing you from these complexities.

When to consider alternatives

  • Long-Running Operations: If your work items are long-running (e.g., several seconds or more), use dedicated threads or the TaskFactory.StartNew method with a custom thread creation option might be more suitable to avoid saturating the ThreadPool and affecting overall application performance.
  • Specialized Thread Requirements: If your tasks require specific thread priority or affinity, you might need to create dedicated threads with the desired settings.

Conclusion
The ThreadPool is a valuable tool for concurrent programming in C#. It offers a convenient and efficient way to manage threads, especially for short-lived, CPU-bound tasks. By understanding how the ThreadPool works and its appropriate use cases, you can create well-structured and performant C# applications.

HostForLIFE ASP.NET Core 9.0 Hosting

European Best, cheap and reliable ASP.NET hosting with instant activation. HostForLIFE.eu is #1 Recommended Windows and ASP.NET hosting in European Continent. With 99.99% Uptime Guaranteed of Relibility, Stability and Performace. HostForLIFE.eu security team is constantly monitoring the entire network for unusual behaviour. We deliver hosting solution including Shared hosting, Cloud hosting, Reseller hosting, Dedicated Servers, and IT as Service for companies of all size.