Introduction
Sometimes you need to run some time consuming operations and you don't want (or just can't) force the user to wait for such operations to complete; in such cases, threads are a good answer but more often than not, implementing threads in existing code may be difficult or bring to "specialized" code which can't be reused; with the following I'm trying to explain a simple, yet effective way to implement multithreading in your existing applications.
Background
I needed exactly what I described above; I was asked to write some code to scan a database table, find out users willing to receive SMS messages related to certain events and send them messages; now, as you can see, such a task is a perfect candidate for some threaded code... but while at it, I decided that not only I wanted some reusable code, but I also wanted to be able to run the same operation "normally" (no threading) if I wanted to... and this can help whenever debugging the code, so, after fiddling a bit, I ended with the code I'm presenting below.
Using the code
Let's say you already have a class performing some task and you want to allow it to run inside a separate thread; after adding my "runner" class code (more below), you'll need to change your class to inherit an interface called "IRunnable
" and implement the two required methods, here's an example client class
using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
namespace ObjRunner
{
class TestClass:ObjRunner.Runner.IRunnable
{
#region "storage"
private bool _running = false;
private bool _stopping = false;
private int _maxvalue = 0;
List<int> _vals;
#endregion
#region "instance"
public TestClass(int maxValue = 1000)
{
this._maxvalue = maxValue;
}
#endregion
#region "properties"
public List<int> Results
{
get { return this._vals; }
}
#endregion
#region "methods"
public int doSomeLongJob()
{
this._running = true;
int max = this._maxvalue;
this._vals = new List<int>((int)(max / (Math.Log(max) - 1.08366)));
List<int> vals = this._vals;
double maxSquareRoot = Math.Sqrt(max);
BitArray eliminated = new BitArray(max + 1);
vals.Add(2);
for (int i = 3; i <= max; i += 2)
{
if (!eliminated[i])
{
if (i < maxSquareRoot)
{
for (int j = i * i; j <= max; j += 2 * i)
eliminated[j] = true;
}
vals.Add(i);
}
if (this._stopping) break;
}
this._running = false;
return 0;
}
#endregion
#region "IRunnable"
public int Start()
{
return doSomeLongJob();
}
public int Stop()
{
this._stopping = true;
while (this._running)
Thread.Sleep(0);
return 0;
}
#endregion
}
}
The above will add the needed "IRunnable
" interface to your class and implements the methods "Run()
" and "Stop()
" the first one will then just call your existing "do your work" code, while the second, which is optional, should implement some method to signal to your "work code" to stop; this latter method implementation is optional (you may leave it empty) but notice that if you won't implement it, you won't be able to perform a "clean shutdown" of your background code; notice that, using such an approach you'll be able to either "thread enable" an existing class or run it "normally" to debug it (by calling the regular "execution method - "doSomeLongJob()" in the above example) and this may be useful to both ease debugging and/or to add thread to some existing application.
Anyhow, the above leverages the code sitting inside the "Runner.cs" class which (surprise, surprise) isn't so complex as you could imagine, here's the code.
using System;
using System.Diagnostics;
using System.Threading;
namespace ObjRunner
{
class Runner
{
#region "storage"
private const int _GRACE_TIME = 15000;
private Thread _worker = null;
private ManualResetEvent _running = null;
private ManualResetEvent _stopping = null;
private int _stopWait = _GRACE_TIME;
private string _runnerID = null;
private IRunnable _objRun = null;
private DateTime _dateStart = DateTime.Now;
private DateTime _dateStop = DateTime.Now;
private Stopwatch _swElapsed = null;
private int _exitCode = 0;
private string _lastError = null;
#endregion
#region "properties"
public string ID
{
get { return this._runnerID; }
}
public object objToRun
{
get { return (object)this._objRun; }
}
public bool isRunning
{
get { return this._running.WaitOne(0); }
}
public DateTime dateStart
{
get { return this._dateStart; }
}
public DateTime dateStop
{
get { return this._dateStop; }
}
public TimeSpan elapsedTime
{
get { return this._swElapsed.Elapsed; }
}
public int exitCode
{
get { return this._exitCode; }
}
public string lastError
{
get { return this._lastError; }
}
#endregion
#region "interface"
public interface IRunnable
{
int Start();
int Stop();
}
#endregion
#region "instance"
public Runner(string ID, object objToRun)
{
initInstance();
this._runnerID = ID;
this._objRun = (IRunnable)objToRun;
}
~Runner()
{
stopChild();
this._objRun = null;
this._worker = null;
}
private void initInstance()
{
this._running = new ManualResetEvent(false);
this._stopping = new ManualResetEvent(false);
this._dateStart = this._dateStop = DateTime.Now;
this._swElapsed = new Stopwatch();
this._exitCode = 0;
this._lastError = null;
this._stopWait = _GRACE_TIME;
}
#endregion
#region "methods"
public bool startChild()
{
try
{
if (null == this._objRun) return false;
if (this._running.WaitOne(0)) return false;
this._stopping.Reset();
this._running.Set();
this._worker = new Thread(new ThreadStart(this.runWorker));
this._worker.Start();
}
catch (Exception ex)
{
traceMsg("Runner::startChild(): {0}", ex.Message);
this._lastError = ex.Message;
this._running.Reset();
return false;
}
return true;
}
public bool stopChild()
{
return stopWorker();
}
#endregion
#region "worker"
private void runWorker()
{
this._dateStart = this._dateStop = DateTime.Now;
this._swElapsed = new Stopwatch();
this._swElapsed.Start();
try
{
int rc = this._objRun.Start();
this._exitCode = rc;
}
catch (ThreadAbortException)
{
traceMsg("Runner::runWorker(): thread Abort() invoked.");
}
catch (Exception ex)
{
traceMsg("Runner::runWorker(): {0}", ex.Message);
this._lastError = ex.Message;
}
finally
{
this._swElapsed.Stop();
this._dateStop = DateTime.Now;
this._running.Reset();
}
}
private bool stopWorker()
{
if (null == this._objRun) return true;
if (null == this._worker) return true;
if (!this._running.WaitOne(0)) return true;
try
{
this._stopping.Set();
if (null != this._objRun)
{
int rc = this._objRun.Stop();
if (0 != rc) this._exitCode = rc;
}
this._worker.Abort();
this._worker.Join(this._stopWait);
}
catch (Exception ex)
{
traceMsg("Runner::stopWorker(): {0}", ex.Message);
this._lastError = ex.Message;
return false;
}
return true;
}
#endregion
#region "utilities"
private void giveTime(int milliSeconds = 0)
{
Thread.Sleep(0);
if (milliSeconds > 0)
Thread.Sleep(milliSeconds);
}
private void traceMsg(string format, params object[] args)
{
Debug.WriteLine(string.Format(format, args));
}
#endregion
}
}
As you can see, the class constructor takes two parameters, the first one is a "unique ID" (you generate it as you want, in my example I used the "timer ticks" but you may use a GUID, some database key or whatever floats your boat) which may be useful,
in case you are running multiple background jobs, to identify a given one; the second one is an object (e.g. your class, as seen above) implementing
the "IRunnable
" interface; to use the class all you'll need to do will then be:
- Instantiate your own "
IRunnable
" class, initialize it as needed - Instantiate a copy of the "
Runner
" class
and pass to it your class - Call the
Runner
class "Start()
" method
so, to put the together the code we already saw, we may have some test console application containing code like this
using System;
using System.Threading;
namespace ObjRunner
{
class Program
{
static int Main(string[] args)
{
int maxValue = 100000000;
if (args.Length > 0)
maxValue = int.Parse(args[0]);
string ID = DateTime.Now.Ticks.ToString("X");
TestClass tc = new TestClass(maxValue);
Runner batch = new Runner(ID, (object)tc);
if (!batch.startChild())
{
printf("StartChild, err={0}\n", batch.lastError);
return 1;
}
printf("Thread is running, hit any key to force stop...\n");
while ((!Console.KeyAvailable) && (batch.isRunning))
Thread.Sleep(0);
if (Console.KeyAvailable)
Console.ReadKey();
if (batch.isRunning)
{
printf("Key pressed, asking the client to stop...\n");
if (!batch.stopChild())
printf("Failed to perform a clean stop !!\n");
else
printf("Client successfully stopped\n");
}
else
printf("Client terminated job.\n");
if (0 == batch.exitCode)
{
printf("Prime number #{0} is {1}\n", tc.Results.Count, tc.Results[tc.Results.Count - 1]);
}
printf("Start={0}\nStop={1}\nElapsed={2}\nrc={3}\n", batch.dateStart, batch.dateStop, batch.elapsedTime, batch.exitCode);
if (!string.IsNullOrEmpty(batch.lastError)) printf("Error={0}\n", batch.lastError);
printf("All done, press a key to exit...\n");
Console.ReadKey();
return 0;
}
private static void printf(string format, params object[] args)
{
Console.Write(string.Format(format, args));
}
}
}
As you see the whole thing is quite simple; the code starts by creating an instance of the desired "IRunnable" class, next it creates an instance of our "runner" and uses it to execute the code in a separate thread; the remainder of the above example is... just an example since it just waits for the thread to complete and, or for the user to press a key; in this latter case, the code proceeds forcing the thread to stop. At end, the code just shows some infos about the execution.
Again... nothing special but the approach may allow to easily add thread support to whatever existing application without the need to write a bunch of code
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.