Introduction
I'd like to present my ThreadPoolComponent
, a simplistic wrapper around the shared ThreadPool
. It may be used to divide a task into a set of threads which are executed concurrently.
Using the Code
- Create an instance of the component, either by coding it yourself or by placing the component onto a control.
- Handle the
ThreadStarted
event. The event handler will be invoked by each thread when it is started and should perform the actual work. - Optionally handle the
ThreadFinished
and AllThreadsFinished
events in case you'd like to be notified. - The component's default behavior is to bind every thread to a single CPU core. Adjust the
FirstAffinityIndex
property to change the behavior. - Invoke one of the component's
Start()
overloads:
Start()
: Starts as many threads as available CPU coresStart(int numThreads)
: Starts the specified number of threadsStart(IEnumerable<T> tags)
: Starts one thread per specified state object (these state objects providing thread-specific in- and/or output are passed to the threads in the ThreadEventArgs
instance used by the ThreadStarted
and ThreadFinished
events)
Here is the initialization code of the associated sample program:
var component = new ThreadPoolComponent();
component.ThreadStarted += new EventHandler<ThreadEventArgs>(component_ThreadStarted);
component.AllThreadsFinished += new EventHandler(component_AllThreadsFinished);
component.Start();
Putting the component onto a control simplifies this procedure further as you only need to double-click on the events to create the handlers. Speaking of which, here's the sample implementation:
void component_ThreadStarted(object sender, ThreadEventArgs e)
{
Console.WriteLine(string.Format("Thread #{0} says hello.", e.ThreadIndex));
}
void component_AllThreadsFinished(object sender, EventArgs e)
{
Console.WriteLine("---\nAll threads are finished.");
}
Points of Interest
The component makes use of the shared ThreadPool
because it usually decreases threading overhead by recycling threads. The maximum number of threads may be increased because the component tries to execute all threads concurrently. Using more threads than CPU cores (returned by the static AvailableCpuCores
property) is probably not a good idea.
Binding a thread to a specific core by setting its affinity usually makes sense because CPU cores usually have separate caches. Avoiding thread hopping across cores prevents caches from being refilled, resulting in higher performance. Don't worry, the thread affinities are reset when a thread finishes, so that other ThreadPool
clients are not affected.
Keep in mind that ThreadPool
threads are background threads, i.e. they do not keep a process alive if all foreground threads exit.
Lastly, you obviously still need to synchronize access to shared objects in the event handlers.
History
- 9th March, 2009: Initial post