C# ThreadPool Usage

ThreadPool manages a group of threads. You can process jobs in parallel in the C# programming language using thread pools. With the ThreadPool class, we constrain threads and update a ProgressBar incrementally. We can use a multiple-core architecture for batch processing.

ThreadPool and progress bar in Windows Forms

This C# tutorial shows how to use the ThreadPool type from System.Threading.

Introduction

Note

The .NET Framework provides us with the System.Threading namespace, which includes the ThreadPool class. This is a static class that you can access directly. It provides us with the essential parts of thread pools. It is an implementation of the common "thread pool" design pattern. 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 entirely 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.

MSDN: 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.

Usage locations

.NET Framework information

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. This is because the threads are already made and are just "hooked up" when required. Here's why ThreadPool-style code is used on servers.

MSDN reference

MSDN: 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.

ThreadPool versus BackgroundWorker

Square abstract illustration

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.

BackgroundWorker Tutorial

Your program does batch processing Consider ThreadPool

Your program makes many (3+) threads Consider ThreadPool

Your program uses Windows Forms Consider BackgroundWorker

Thread considerations. Also the specifics of how you use your threads can help you find the best code. This next table compares the threading scenarios and which class is best.

You need one extra thread Use BackgroundWorker

You have many short-lived threads Use ThreadPool

Requirements

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.

Hook up methods

Method call

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? You must use WaitCallback. At MSDN, 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

Here we note that 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.

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 = thread.SelectedIndex;
    }
}

What's going on. We are sending two values to the ProcessFile threaded method. It needs to know the FileName and the SelectedIndex, and we send all this in the object parameter.

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. 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 be halfway done.

ProgressBar Example: Windows Forms

Call Invoke on 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.

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.
	}
    }
}

Delegate syntax. Near the start of the above code, you will see the delegate UpdateBar declared. This syntax is strange but it tells Visual Studio and C# that you need to use the method as an object.

Delegate TutorialProgress bar with threads completed

Note: The above program demonstrates how you can set the Maximum and Minimum on the ProgressBar, and how you can Invoke the delegate method after the work is done to increment the size of the ProgressBar.

Threads in debugger

Visual Studio logo (Copyright Microsoft)

Here I show how to look at the threads in the Visual Studio 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. Run the debugger with the green arrow and when the threads are running, hit the 'pause' button in the toolbar.

Next steps. 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.

Screenshot of ThreadPool debugging

Note: The above image shows ten threads total, but four of the worker threads are the ones in my program that are assigned to MainWindow.ProcessFile.

Constrain worker threads

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. With this thread count field, you will need to use a lock in the C# language to avoid having the field read or written incorrectly. Locks shield your thread from changes in other threads.

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...
}
Lock keyword

What we see. The above code is the method executed asynchronously. It will not start its work until there are fewer than four other worker threads. This is good for a quad-core machine. Please see the article that describes the lock statement for more context.

Lock Statement

Control thread counts

You can use SetMinThreads on ThreadPool to improve the throughput and performance in bursts of activity. I have material on the best number of minimum threads to use.

ThreadPool.SetMinThreads Method

Summary

The C# programming language

Here we saw how you can apply the ThreadPool class to effectively manage many threads in your C# programs. Progress bars and fast UIs on Windows Forms applications are very impressive and not extremely difficult. However, threads introduce lots of complexity and lead to bugs. ThreadPool is a useful simplification, but it is still difficult.

Thread Overview
.NET