Introduction
This is a quick article on how to apply a nice easy base class to the age-old problem of getting your classes to support internal worker threads in a thread-safe manner. What I wanted was a base class that lets an internal thread start and stop sensibly, and have an abstract callback method that allows flexible 'units of work' to be executed.
Background
Often, you need a class that has it's own worker thread. Well, I do. Maybe its just me. And often, I see people using bool to indicate thread running, or, thread should stop. Things like:
private static Thread _thread;
private static bool _isRunning;
static void Main()
{
_thread = new Thread(threadMethod);
_thread.Start();
_isRunning = true;
Console.WriteLine("Any key to stop");
Console.ReadKey();
_isRunning = false;
while (_thread.IsAlive)
{
Console.WriteLine("Wait for stop");
Thread.Sleep(200);
}
}
static void threadMethod()
{
while (_isRunning)
{
Console.WriteLine("Thread running");
Thread.Sleep(1000);
}
}
This is OK per-se, but its not very elegant. Its not thread safe. In short, 'could do better'.
Using the Code
- So the design called for (in my mind, at least):
- Abstract base class.
ManualResetEvent
objects to indicate that the thread is running, and that it has been asked to stop. - Threadsafe Start and Stop methods.
- An abstract callback to be implemented by the inheriting class, that will be called from the thread on a per-tick basis.
- Some useful events like 'thread finished' event.
Also my immediate use-case required that the worker be able to do small chunks of work repetiviely, so as to allow a large process to be split up such that it can be stopped in a timely manner. For instance, consider a process to load stock data from a feed into a database. Each 'unit of work' might be to check if there is new data available for a given stock, and if so, read it from the feed and write it to the database. (Obviously this is simplistic, but in some cases you don't get event driven nicely architected 3rd party products...). You'd want to have each stock read in one unit of work, as reading 100 or so would mean the 'stop' command to the thread would take ages to respond...
So, without further ado, heres the base class as it stands today:
public abstract class ManagedThread
{
private Thread thread;
protected ManualResetEvent mreAskStop;
private ManualResetEvent mreInformStopped;
public delegate void ThreadFinishedEventHandler();
public event ThreadFinishedEventHandler ThreadFinishedEvent;
private object _lockObject = new object();
private void RaiseFinishedEvent()
{
if (ThreadFinishedEvent != null)
{
ThreadFinishedEvent();
}
}
public ManagedThread(string name)
{
Name = name;
mreAskStop = new ManualResetEvent(false);
mreInformStopped = new ManualResetEvent(false);
}
public virtual void Start()
{
lock (_lockObject)
{
if (thread != null)
{
return;
}
mreAskStop.Reset();
mreInformStopped.Reset();
thread = new Thread(WorkerCallback);
thread.IsBackground = true;
thread.Start();
}
}
protected abstract void DoUnitOfWork(ref int tick);
protected int TickDefaultMilliseconds = 1000;
protected int TickCount;
protected virtual void WorkerCallback()
{
for (; ; )
{
DoUnitOfWork(ref TickCount);
TickCount--;
Thread.Sleep(TickDefaultMilliseconds);
if (mreAskStop.WaitOne(0, true))
{
mreInformStopped.Set();
break;
}
}
RaiseFinishedEvent();
}
public bool HasStopped()
{
return mreInformStopped.WaitOne(1, true);
}
public bool IsAlive
{
get { return thread != null && thread.IsAlive; }
}
public virtual void Stop()
{
lock (_lockObject)
{
if (thread != null && thread.IsAlive)
{
mreAskStop.Set();
while (thread.IsAlive)
{
if (mreInformStopped.WaitOne(100, true))
{
break;
}
}
thread = null;
}
}
}
public string Name { get; private set; }
}
Points of Interest
- The exit of the worker thread will call the Thread Finished event, which is nice.
- The thread is marked as a Worker thread so that it dies with the owning process.
- There is a lock object used to prevent re-entrancy.
- The default millisecond tick can be altered to make the calls to the
DoUnitOfWork
less granular.
Usage
Taking the aforementioned stock reader example, we could have a derived class
ReadStocks
, like this:
public class ReadStock : ManagedThread
{
private const int readStockIntervalInSeconds = 5;
public ReadStock() : base("Read Stock"){}
protected override void DoUnitOfWork(ref int tick)
{
if (tick == 0)
{
tick = readStockIntervalInSeconds;
}
}
}
And call this like so:
ReadStock readStock = new ReadStock();
readStock.Start();
Console.WriteLine("Any key to stop");
Console.ReadKey();
readStock.Stop();
And finally, if you want this to just run as a 'fire once', or to stop after a certain number of iterations, the Stop()
command can be called from within the DoUnitOfWork
method:
public class ReadStock : ManagedThread
{
public ReadStock() : base("Do one thing only"){}
protected override void DoUnitOfWork(ref int tick)
{
...
Stop();
}
}
Or:
public class ReadStock : ManagedThread
{
private const int eventIntervalInSeconds = 20;
private const int numberOfTimesToDoThis = 5;
private int eventCounter;
public ReadStock() : base("Do it five times")
{
eventCounter = 0;
}
protected override void DoUnitOfWork(ref int tick)
{
if (tick == 0)
{
tick = readStockIntervalInSeconds;
...
if (eventCounter++ > numberOfTimesToDoThis)
{
Stop();
}
}
}
}