Background
I've previously discussed the differences between the BackgroundWorker and Thread classes, but I figured it would be useful to touch on some code. I'd like to share the pattern
I commonly use when creating threads in C# and discuss some of the highlights.
The Single Thread
I like to use this design when I have a single thread I need to run and in the context of my object responsible for running the thread, I do mean having a single thread.
Of course, you could have your object in control of multiple threads as long as you repeat this design pattern for each of them.
Here's the interface that I'll be using for all of the examples:
internal interface IThreadRunner
{
#region Exposed Members
void Start();
void Stop();
#endregion
}
Behold! The single thread runner!
internal class SingleThreadRunner : IThreadRunner
{
#region Fields
private readonly object _threadLock;
private readonly AutoResetEvent _trigger;
private Thread _theOneThread;
#endregion
#region Constructors
private SingleThreadRunner()
{
_threadLock = new object();
_trigger = new AutoResetEvent(false);
}
#endregion
#region Exposed Members
public static IThreadRunner Create()
{
return new SingleThreadRunner();
}
public void Start()
{
lock (_threadLock)
{
if (_theOneThread != null)
{
return;
}
_theOneThread = new Thread(DoWork);
_theOneThread.Name = "The One Thread";
_theOneThread.Start(_trigger);
}
}
public void Stop()
{
lock (_threadLock)
{
if (_theOneThread == null)
{
return;
}
_theOneThread = null;
_trigger.Set();
}
}
#endregion
#region Internal Members
private void DoWork(object parameter)
{
var currentThread = Thread.CurrentThread;
var trigger = (AutoResetEvent)parameter;
try
{
while (currentThread == _theOneThread)
{
Console.WriteLine("Awesome work being done.");
trigger.WaitOne(1000);
}
}
finally
{
lock (_threadLock)
{
if (_theOneThread == currentThread)
{
_theOneThread = null;
}
}
}
}
#endregion
}
This design was taken from some Java programming I had done in a previous life. Essentially, I have a thread that is responsible for doing some work in a loop.
It could be anything... Periodically polling for some data, a work dequeing thread, a random-cursor-moving thread... Anything! The point is, you only want one
of these suckers hanging around. How is this accomplished?
Leveraging the instance variable that marks the one expected running thread is key here. Whenever this thread checks if it should still be running,
if the current thread doesn't match what's assigned to the instance variable then it needs to stop! This means you could potentially spawn off two of these threads,
and if you set the instance variable to one of the two, then the other one should kill itself off! Pretty neat.
By using the reset event, we can actually interrupt this thread if it's sleeping. This is great if we have a thread that periodically wakes up to do some work but we want
to stop it and have it stop fast. We simple set our instance variable for the thread to be null and then set this thread's reset event to ensure it get's woken up.
Presto! It wakes up, checks the condition, and realizes it needs to exit the loop.
Simple.
The Handful of Threads
This design is almost identical to the single thread design above. I use it primarily when I want to have an object responsible for a bunch of threads that are turned on/off
under the same conditions. The major difference between the two designs? In the single thread scenario, we check that our current thread is still set to be the one instance.
In this design, we need all of our threads to be checking against the same state object which is not going to be a single thread instance.
Let's have a peek:
internal class GroupThreadRunner : IThreadRunner
{
#region Fields
private readonly object _threadLock;
private readonly Dictionary<Thread, AutoResetEvent> _triggers;
private bool _running;
#endregion
#region Constructors
private GroupThreadRunner()
{
_threadLock = new object();
_triggers = new Dictionary<Thread, AutoResetEvent>();
}
#endregion
#region Exposed Members
public static IThreadRunner Create()
{
return new GroupThreadRunner();
}
public void Start()
{
lock (_threadLock)
{
if (_triggers.Count > 0)
{
return;
}
_running = true;
const int NUMBER_OF_THREADS = 4;
for (int i = 0; i < NUMBER_OF_THREADS; ++i)
{
var thread = new Thread(DoWork);
thread.Name = "Thread " + i;
var trigger = new AutoResetEvent(false);
_triggers[thread] = trigger;
thread.Start(trigger);
}
}
}
public void Stop()
{
lock (_threadLock)
{
if (_triggers.Count <= 0)
{
return;
}
_running = false;
foreach (var trigger in _triggers.Values)
{
trigger.Set();
}
}
}
#endregion
#region Internal Members
private void DoWork(object parameter)
{
var currentThread = Thread.CurrentThread;
var trigger = (AutoResetEvent)parameter;
try
{
while (_running)
{
Console.WriteLine("Awesome work being done by " + currentThread.Name);
trigger.WaitOne(1000);
}
}
finally
{
lock (_threadLock)
{
_triggers.Remove(currentThread);
if (_running && _triggers.Count <= 0)
{
_running = false;
}
}
}
}
#endregion
}
Summary
The above patterns I discussed cover my common usage for threads: Instances that have reoccurring work over long periods of time. Both patterns are very similar and only have
slight modifications to make them support one instance or many thread instances running. If you have one unique thread or many threads... there's a pattern for you!
Check out a full working example of this code over here: