So, in my "day" job I'm a project manager for a grant funded educational software system... This system was written over the years by a series of consultants with no real tech guidance at all. That being the case, when I joined the project I was handed about 500,000 lines of crap spaghetti code and wanna-be superman heroics.
You know what I'm talking about! :(
At any rate, one component of this software suite is a multi-threaded windows service that handles batch processing based on batch queue tables in SQL server. The problem is, the thread pooling never really worked as intended, and it was written in .Net 1.1. Time for a change of pace.
My goal was to have an object encapsulate the batch processing, that gets run on it's own thread, and reports back to the worker when it's done. So, I have one worker thread that's responsible for spinning up x number of process threads as needed based on the max number of threads available, and the number of items in the batch queue table.
Multi-threading is one of those things that's tricky to begin with, and if you don't use it every day, it can cause you to consume massive quantities of ibuprofen. Rather then just hack away at writing the full blown application, I made myself a nice little "proof of concept".. which of course, I share with you :)
ProcessObject.CS
internal class ProcessObject
{
internal delegate void ProcessThreadStartDelegate();
internal delegate void ProcessThreadStopDelegate();
internal delegate void ProcessThreadMessageDelegate(string Message);
internal event ProcessThreadStartDelegate ProcessThreadStart;
internal event ProcessThreadStopDelegate ProcessThreadStop;
internal event ProcessThreadMessageDelegate ProcessThreadMessage;
public void DoThreadProcess()
{
RaiseProcessThreadStart();
for (int x = 1; x < 10; x++)
{
System.Threading.Thread.Sleep(55);
RaiseProcessThreadMessage(System.Threading.Thread.CurrentThread.ManagedThreadId.ToString() + " : " + x.ToString() + " Message From Thread");
}
RaiseProcessThreadStop();
}
private void RaiseProcessThreadMessage(string msg)
{
if (ProcessThreadMessage != null)
{
ProcessThreadMessage(msg);
}
}
private void RaiseProcessThreadStart()
{
if (ProcessThreadStart != null)
{
ProcessThreadStart();
}
}
private void RaiseProcessThreadStop()
{
if (ProcessThreadStop != null)
{
ProcessThreadStop();
}
}
}
WorkerProcess.CS
public class WorkerProcess
{
private int _threadCount = 0;
public int ThreadCount
{
get { return _threadCount; }
set
{
lock (this)
{
_threadCount = value;
}
}
}
public void StartProcess()
{
for (int x = 0; x < 5; x++)
{
this.ThreadCount++;
ProcessObject myObj = ProcessFactory.GetProcessObject(this);
System.Threading.ThreadStart processStart = new System.Threading.ThreadStart(myObj.DoThreadProcess);
System.Threading.Thread processThread = new System.Threading.Thread(processStart);
processThread.Start();
}
}
internal void HandleProcessMessage(string msg)
{
Console.WriteLine(msg);
}
internal void HandleProcessStart()
{
Console.WriteLine("Got Thread Start!! : " + this.ThreadCount.ToString());
}
internal void HandleProcessStop()
{
this.ThreadCount--;
Console.WriteLine("Got Thread Stop!! : " + this.ThreadCount.ToString());
}
}
ProcessFactory.CS
internal static class ProcessFactory
{
internal static ProcessObject GetProcessObject(WorkerProcess worker)
{
ProcessObject newObj = new ProcessObject();
newObj.ProcessThreadStart += new ProcessObject.ProcessThreadStartDelegate(worker.HandleProcessStart);
newObj.ProcessThreadStop += new ProcessObject.ProcessThreadStopDelegate(worker.HandleProcessStop);
newObj.ProcessThreadMessage += new ProcessObject.ProcessThreadMessageDelegate(worker.HandleProcessMessage);
return newObj;
}
}
Now, what happens is WorkerProcess get's spun up by the main application (I just dropped it into a console application for this demo) and WorkerProcess::StartProcess() is called.
That method makes requests to the static ProcessFactory, using itself as a parameter for the creation of a new Process Object. The ProcessFactory is responsible for creating the process object and wiring it's events to the worker process. Once the factory returns a created process object, the worker process can then spin up a new thread and have it execute the ProcessObject::DoThreadProcess().
Now, I am by no means a threading expert.. and the context if this sample should not be taking as gospel... There are several thread management pieces missing, such as health monitoring, stopping processobjects, closing processes before worker dies, etc, etc. Those, I'll leave as an exercise for the reader :)
Full Source Download