C# ThreadPool

Use the ThreadPool type from System.Threading. Create a WaitCallback object.

ThreadPool. This type manages a group of threads. We process jobs in parallel using thread pools. With the ThreadPool class, we constrain threads and incrementally update a ProgressBar.Threads

Intro. The .NET Framework provides us with the System.Threading namespace, which includes the ThreadPool class. This is a static class that you can directly access. It provides us with the essential parts of thread pools.
Tip: It is useful for running many separate tasks in the background. There are better options for a single background thread.

Maximum number of threads. This is usually useless to know. The whole point of ThreadPool in .NET is that it internally manages the threads in the ThreadPool. Multiple-core machines will have more threads than older machines.
Quote: Thread pools typically have a maximum number of threads. If all the threads are busy, additional tasks are placed in queue until they can be serviced as threads become available (Microsoft Docs).

Usage. The ThreadPool type can be used on servers and in batch processing applications. ThreadPool has internal logic that makes getting a thread much less expensive. Threads are already made and are just "hooked up" when required.
Quote: Thread pools are often employed in server applications. Each incoming request is assigned to a thread from the thread pool, so the request can be processed asynchronously, without tying up the primary thread or delaying the processing of subsequent requests (Microsoft Docs).

Threading is important but for most apps that don't take a long time to execute and are only doing one thing, it is not important. For applications whose interface usability isn't important, avoid threads as well.

BackgroundWorker. If you are using Windows Forms, prefer the BackgroundWorker for simpler threading requirements. BackgroundWorker does well with network accesses and other simple stuff. For batch processing with many processors, you need ThreadPool.
The program does batch processing. Consider ThreadPool. This better automates multiple short-lived threads.
It makes three or more threads. Also consider ThreadPool, which handles multiple threads, not just one.
It uses Windows Forms. Consider BackgroundWorker, an easy-to-use control that you can add in the Visual Studio designer.

Thread considerations. The specifics of how you use your threads can help you determine which objects to use. This next section compares threading scenarios. It indicates which class is best.
You need one extra thread. Use BackgroundWorker. Also consider just using a Thread object and the ThreadStart method.
You have many short-lived threads. Use ThreadPool—the alternative is to implement your own ThreadPool with Thread objects. This is harder.

Methods. You can hook up methods to the ThreadPool by using QueueUserWorkItem. You have your method you want to run on the threads. And you must hook it up to QueueUserWorkItem. How can you do this?

We must use WaitCallback. WaitCallback is described as a delegate callback method to be called when the ThreadPool executes. It is a delegate that "calls back" its argument.

WaitCallback. You can use WaitCallback by simply specifying the "new WaitCallback" syntax as the first argument to ThreadPool.QueueUserWorkItem. You don't need any other code to make this approach effective.
Example that uses WaitCallback: C# void Example() { // Hook up the ProcessFile method to the ThreadPool. // Note: 'a' is an argument name. Read more on arguments. ThreadPool.QueueUserWorkItem(new WaitCallback(ProcessFile), a); } private void ProcessFile(object a) { // I was hooked up to the ThreadPool by WaitCallback. }

Parameters. You can use parameters by defining a special class and putting your important values inside of it. Then, the object is received by your method, and you can cast it. Here's an example that builds on the earlier ones.
Here: We are sending 2 values to the ProcessFile threaded method. The object has contains the FileName and SelectedIndex.
Example that uses QueueUserWorkItem with argument: C# // Special class that is an argument to the ThreadPool method. class ThreadInfo { public string FileName { get; set; } public int SelectedIndex { get; set; } } class Example { public Example() { // Declare a new argument object. ThreadInfo threadInfo = new ThreadInfo(); threadInfo.FileName = "file.txt"; threadInfo.SelectedIndex = 3; // Send the custom object to the threaded method. ThreadPool.QueueUserWorkItem(new WaitCallback(ProcessFile), threadInfo); } private void ProcessFile(object a) { // Constrain the number of worker threads // (Omitted here.) // We receive the threadInfo as an uncasted object. // Use the 'as' operator to cast it to ThreadInfo. ThreadInfo threadInfo = a as ThreadInfo; string fileName = threadInfo.FileName; int index = threadInfo.SelectedIndex; } }

ProgressBar. You can use the ProgressBar by adding the Windows Forms control in the Toolbox panel on the right to your Windows program in the designer. Next, you have to deal with progressBar1.Value, progressBar1.Minimum and progressBar1.Maximum.
Note: The Value is your position between the minimum and the maximum. Initialize your ProgressBar like this.
Example that sets ProgressBar: C# // Set progress bar length. // Here we have 6 units to complete, so that's the maximum. // Minimum usually starts at zero. progressBar1.Maximum = 6; // or any number progressBar1.Minimum = 0;

ProgressBar position. The length of the colored part of your ProgressBar is the Value's percentage of the Maximum. So, if the Maximum is 6, a Value of 3 will display as halfway done.

Invoke, ProgressBar. Let's look at how to use the Invoke method on the ProgressBar instance. Unfortunately, you can't access Windows controls on worker threads, as the UI thread is separate. We have to use a delegate and Invoke onto the ProgressBar.
UpdateBar: We see the delegate UpdateBar declared. This delegate syntax is different. It indicates that you need to use the method as an object.
So: We set the Maximum and Minimum on the ProgressBar. We invoke the delegate method after the work is completed to increment the size.
Example that calls Invoke: C# public partial class MainWindow : Form { // This is the delegate that runs on the UI thread to update the bar. public delegate void BarDelegate(); // The form's constructor (autogenerated by Visual Studio) public MainWindow() { InitializeComponent(); } // When a buttom is pressed, launch a new thread private void button_Click(object sender, EventArgs e) { // Set progress bar length. progressBar1.Maximum = 6; progressBar1.Minimum = 0; // Pass these values to the thread. ThreadInfo threadInfo = new ThreadInfo(); threadInfo.FileName = "file.txt"; threadInfo.SelectedIndex = 3; ThreadPool.QueueUserWorkItem(new WaitCallback(ProcessFile), threadInfo); } // What runs on a background thread. private void ProcessFile(object a) { // (Omitted) // Do something important using 'a'. // Tell the UI we are done. try { // Invoke the delegate on the form. this.Invoke(new BarDelegate(UpdateBar)); } catch { // Some problem occurred but we can recover. } } // Update the graphical bar. private void UpdateBar() { progressBar1.Value++; if (progressBar1.Value == progressBar1.Maximum) { // We are finished and the progress bar is full. } } }

Debugger. Once you have a program working, you can take these steps to visualize the threads. First, open your threaded app in debug mode. Once your application is running in the debugger, tell it to do its job and run the threads.
So: Run the debugger with the green arrow and when the threads are running, hit the pause button in the toolbar.

Next steps. Please go to Debug > Windows > Threads. This menu item will open a window that looks like the one here. You can see exactly how many threads are running in the ThreadPool.
Note: The image shows ten threads total, but four of the worker threads are assigned to MainWindow.ProcessFile.

Counts. If you have a dual-core or quad-core system, you will want at most two or four demanding threads. We can do this by keeping a _threadCount field and tracking the number of running threads.
Tip: With this thread count field, you will need to use a lock in the C# language to avoid having the field incorrectly read or written.
Info: We see that the method was asynchronously executed. It will not start its work until there are fewer than four other worker threads.
Counts: You can use SetMinThreads on ThreadPool to improve the throughput and performance in bursts of activity.
Example that counts threads: C# // Lock on this object. readonly object _countLock = new object(); private void ProcessFile(object argument) { // Constrain the number of worker threads while (true) { // Prevent other threads from changing this under us lock (_countLock) { if (_threadCount < 4) { // Start the processing _threadCount++; break; } } Thread.Sleep(50); } // Do work... }

Summary. We applied the ThreadPool class to effectively manage many threads. Progress bars and fast UIs on Windows Forms applications are impressive. Threads introduce complexity.

© 2007-2020 Sam Allen. Every person is special and unique. Send bug reports to info@dotnetperls.com.