Click here to Skip to main content
Click here to Skip to main content

Spice up your code using threads

, 3 Oct 2012
Rate this:
Please Sign up or sign in to vote.
How to easily add multithreading to your C# code.

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"
        // running and stop flags
        private bool    _running = false;
        private bool    _stopping = false;
        
        // internal stuff used to do our job
        private int     _maxvalue = 0;

        List<int>       _vals;
        #endregion

        #region "instance"
        /// <summary>
        /// thread testing class
        /// </summary>
        /// <param name="maxValue">
        /// optional, max value for calculations
        /// </param>
        public TestClass(int maxValue = 1000)
        {
            this._maxvalue = maxValue;
        }
        #endregion

        #region "properties"
        public List<int> Results
        {
            get { return this._vals; }
        }
        #endregion

        #region "methods"
        /// <summary>
        /// public method, can be invoked directly during debugging 
        /// </summary>
        /// <returns>
        /// 0 if all ok
        /// </returns>
        public int doSomeLongJob()
        {
            this._running = true;
            
            // just a test, run the "Sieve of Eratosthenes"
            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"
        /// <summary>
        /// IRunnable "Start()" method called from thread
        /// </summary>
        /// <returns>
        /// 0 if all ok
        /// </returns>
        public int Start()
        {
            // run the "main" code
            return doSomeLongJob();
        }

        /// <summary>
        /// IRunnable "Stop()" method, gracefully stop the code
        /// </summary>
        /// <returns>
        /// 0 if all ok
        /// </returns>
        public int Stop()
        {
            // signal stop and wait until stopped
            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
{
    // ObjRunner: allows to run code in a background task just by implementing the
    //            IRunnable interface and with little changes to existing code
    //
    // ref:
    // http://www.daniweb.com/software-development/csharp/threads/175091/class-interface
    //
    class Runner
    {
        #region "storage"
        // grace time to stop a thread
        private const int           _GRACE_TIME = 15000;

        // thread stuff
        private Thread		        _worker = null;
        private ManualResetEvent    _running = null;
        private ManualResetEvent    _stopping = null;
        private int                 _stopWait = _GRACE_TIME;

        // ID and stats informations
        private string		        _runnerID = null;
        private IRunnable	        _objRun = null;
        private DateTime	        _dateStart = DateTime.Now;
        private DateTime	        _dateStop = DateTime.Now;
        private Stopwatch	        _swElapsed = null;
        
        // exit code from the IRunnable object
        private int			        _exitCode = 0;

        // last error message (if any)
        private string		        _lastError = null;
        #endregion

        #region "properties"
        /// <summary>
        /// ID for this instance, useful to identify a given one
        /// </summary>
        public string ID
        {
            get { return this._runnerID; }
        }

        /// <summary>
        /// the IRunnable object we're executing
        /// </summary>
        public object objToRun
        {
            get { return (object)this._objRun; }
        }

        /// <summary>
        /// true = the thread is running
        /// </summary>
        public bool isRunning
        {
            get { return this._running.WaitOne(0); }
        }

        /// <summary>
        /// start date/time 
        /// </summary>
        public DateTime dateStart
        {
            get { return this._dateStart; }
        }

        /// <summary>
        /// stop date/time
        /// </summary>
        public DateTime dateStop
        {
            get { return this._dateStop; }
        }

        /// <summary>
        /// total elapsed time
        /// </summary>
        public TimeSpan elapsedTime
        {
            get { return this._swElapsed.Elapsed; }
        }

        /// <summary>
        /// return code from child
        /// </summary>
        public int exitCode
        {
            get { return this._exitCode; }
        }

        /// <summary>
        /// last internal error message (if any)
        /// </summary>
        public string lastError
        {
            get { return this._lastError; }
        }
        #endregion

        #region "interface"
        /// <summary>
        /// interface to be implemented by runnable objects
        /// </summary>
        public interface IRunnable
        {
            /// <summary>
            /// start the object code from thread
            /// </summary>
            /// <returns>
            /// 0 if all ok
            /// </returns>
            int Start();

            /// <summary>
            /// asks the object for a clean (and quick) stop
            /// </summary>
            /// <returns>
            /// 0 if all ok
            /// </returns>
            int Stop();
        }
        #endregion

        #region "instance"
        /// <summary>
        /// Creates an instance of the Runner class
        /// </summary>
        /// <param name="ID">
        /// ID for this instance 
        /// </param>
        /// <param name="objToRun">
        /// object to be executed (implementing IRunnable)
        /// </param>
        public Runner(string ID, object objToRun)
        {
            initInstance();
            this._runnerID = ID;
            this._objRun = (IRunnable)objToRun;
        }

        /// <summary>
        /// Destroy this instance
        /// </summary>
        ~Runner()
        {
            stopChild();
            this._objRun = null;
            this._worker = null;
        }

        /// <summary>
        /// Initializes this instance internal data
        /// </summary>
        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"
        /// <summary>
        /// spawns a thread calling the IRunnable.Run() method of the child object
        /// </summary>
        /// <returns>
        /// true if the thread was successfully started
        /// </returns>
        public bool startChild()
        {
            try
            {
                // perform some sanity checks and prepare to run
                if (null == this._objRun) return false;
                if (this._running.WaitOne(0)) return false;
                this._stopping.Reset();
                this._running.Set();
                // create and start the thread
                this._worker = new Thread(new ThreadStart(this.runWorker));
                this._worker.Start();
            }
            catch (Exception ex)
            {
                // got an exception, signal and record it
                traceMsg("Runner::startChild(): {0}", ex.Message);
                this._lastError = ex.Message;
                this._running.Reset();
                return false;
            }

            return true;
        }

        /// <summary>
        /// signals the worker thread (and the child) to stop
        /// </summary>
        public bool stopChild()
        {
            // stop the worker
            return stopWorker();
        }
        #endregion

        #region "worker"
        /// <summary>
        /// worker thread function, invokes the IRunnable.Run() method
        /// </summary>
        private void runWorker()
        {
            // setup the start infos
            this._dateStart = this._dateStop = DateTime.Now;
            this._swElapsed = new Stopwatch();
            this._swElapsed.Start();
            try
            {
                // call the child "Run()" method
                int rc = this._objRun.Start();
                this._exitCode = rc;
            }
            catch (ThreadAbortException)
            {
                // we're asked to stop (abort)
                traceMsg("Runner::runWorker(): thread Abort() invoked.");
            }
            catch (Exception ex)
            {
                // got an exception, record and signal it
                traceMsg("Runner::runWorker(): {0}", ex.Message);
                this._lastError = ex.Message;
            }
            finally
            {
                // all done, cleanup and return
                this._swElapsed.Stop();
                this._dateStop = DateTime.Now;
                this._running.Reset();
            }
        }

        /// <summary>
        /// stops the child and the worker thread
        /// </summary>
        private bool stopWorker()
        {
            // sanity checks
            if (null == this._objRun) return true;
            if (null == this._worker) return true;
            
            // is the thread running ?
            if (!this._running.WaitOne(0)) return true;

            // ok, try to perform a clean shutdown
            try
            {
                // signal stop and ask the client to cleanly stop
                this._stopping.Set();
                if (null != this._objRun)
                {
                    int rc = this._objRun.Stop();
                    if (0 != rc) this._exitCode = rc;
                }
                // ensure to terminate the thread
                this._worker.Abort();
                this._worker.Join(this._stopWait);
            }
            catch (Exception ex)
            {
                // got an exception, record and signal it
                traceMsg("Runner::stopWorker(): {0}", ex.Message);
                this._lastError = ex.Message;
                return false;
            }
            return true;
        }
        #endregion

        #region "utilities"
        /// <summary>
        /// release time to the system / waits for the given time
        /// </summary>
        /// <param name="milliSeconds">
        /// milliseconds to wait; 0=no wait
        /// </param>
        private void giveTime(int milliSeconds = 0)
        {
            // release time, optionally wait
            Thread.Sleep(0);
            if (milliSeconds > 0)
                Thread.Sleep(milliSeconds);
        }

        /// <summary>
        /// debug/trace messages
        /// </summary>
        /// <param name="format">
        /// format string used for the message
        /// </param>
        /// <param name="args">
        /// message arguments
        /// </param>
        private void traceMsg(string format, params object[] args)
        {
            // format and emit the message (dbgview...)
            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: 

  1. Instantiate your own "IRunnable" class, initialize it as needed
  2. Instantiate a copy of the "Runner" class and pass to it your class
  3. 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
    {

        /// <summary>
        ///  Runner class demo
        /// </summary>
        /// <param name="args">
        /// not used
        /// </param>
        static int Main(string[] args)
        {
            int maxValue = 100000000;
            if (args.Length > 0)
                maxValue = int.Parse(args[0]);

            // setup an ID, initialize our class and
            // the runner object
            string ID = DateTime.Now.Ticks.ToString("X");
            TestClass tc = new TestClass(maxValue);
            Runner batch = new Runner(ID, (object)tc);

            // start the thread
            if (!batch.startChild())
            {
                printf("StartChild, err={0}\n", batch.lastError);
                return 1;
            }
            
            // wait until completion or until asked to force stop (keypress)
            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 asked to force stop...
            if (batch.isRunning)
            {
                // signal stop
                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");

            // show results
            if (0 == batch.exitCode)
            {
                printf("Prime number #{0} is {1}\n", tc.Results.Count, tc.Results[tc.Results.Count - 1]);
            }

            // dump some infos about the thread execution
            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);

            // all done, wait for a key and exit
            printf("All done, press a key to exit...\n");
            Console.ReadKey();
            return 0;
        }


        // print a formatted string to console
        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 Smile | :) 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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

ObiWan_MCC

Italy Italy
No Biography provided

Comments and Discussions

 
GeneralMy vote of 3 PinmemberJohn Brett2-Oct-12 20:50 
GeneralRe: My vote of 3 PinmemberObiWan_MCC3-Oct-12 5:32 
GeneralRe: My vote of 3 Pinmemberpoint643-Oct-12 7:21 
Good article. When you mention "minimal changes to the existing code", have you tried replacing "new Thread" with "new Task" ? With that minor change, you could take advantage of TPL.
Thumbs Up | :thumbsup:
GeneralMy vote of 4 PinmemberAl-Samman Mahmoud2-Oct-12 10:17 
Questionsome comments Pinmemberjpmik2-Oct-12 9:54 
AnswerRe: some comments PinmemberObiWan_MCC2-Oct-12 21:02 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140721.1 | Last Updated 3 Oct 2012
Article Copyright 2012 by ObiWan_MCC
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid