C# ThreadPool

ThreadPool 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. We can use a multiple-core architecture for batch processing.

ProgressBarThreadPool and progress bar

Intro

Note

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.

Steps

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.

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

Usage

Framework: NET

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.

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.

Thread Pool: MSDN

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

Threads

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.

BackgroundWorkerAbstract squares

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.

ThreadStart

You have many short-lived threads.Use ThreadPool—the alternative is to implement your own ThreadPool with Thread objects. This is harder.

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?

We 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

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

In this example, we are sending two values to the ProcessFile threaded method. It needs to know the FileName and the SelectedIndex. We send all this information in the object parameter.

ProgressBar

Programming tip

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.

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 delegate syntax is different. It indicates that you need to use the method as an object.

DelegatesProgress bar with threads completed

So:We set the Maximum and Minimum on the ProgressBar. We invoke the delegate method after the work is completed to increment the size.

Debugger

Visual Studio logo

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.

Screenshot of ThreadPool debugging

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

Counts

This section provides information

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 incorrectly read or written. 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

We see that the method was asynchronously executed. It will not start its work until there are fewer than four other worker threads. This may be beneficial for a quad-core machine.

Note:Please see the article that describes the lock statement for more information.

Lock Statement

Counts:You can use SetMinThreads on ThreadPool to improve the throughput and performance in bursts of activity.

ThreadPool.SetMinThreads

Summary

C# programming language

We applied the ThreadPool class to effectively manage many threads in 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.


C#: .NET: Thread