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

Tagged as

A Base Class to Allow Start/Stop Threading Safely

, 21 Apr 2011
Rate this:
Please Sign up or sign in to vote.
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

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)
                {
                    // already running state
                    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)
            {
                // todo fix faulted state exception
                if (thread != null && thread.IsAlive) // thread is active
                {
                    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;
 
                // check 3rd party product, is there new data?
 
                // if yes:
                //   read the stock from the 3rd party product            
                //   write it to the database
            }
        }
    }

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)
        {
            // do something only once
            ...
            
            // call stop to exit thread
            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;
 
                // Do the thing you want to do
                ...
 
                // and stop if its done enough times
                if (eventCounter++ > numberOfTimesToDoThis)
                {
                    Stop();
                }
            }
        }
    }

License

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

Share

About the Author

Gizz
Architect
United Kingdom United Kingdom
No Biography provided

Comments and Discussions

 
GeneralNot convinced PinmemberWilliam E. Kempf21-Apr-11 10:15 
GeneralAye, agree PinmemberGizz21-Apr-11 11:42 
GeneralRe: Not convinced PinmemberSnoepie25-Apr-11 6:38 

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.140821.2 | Last Updated 21 Apr 2011
Article Copyright 2011 by Gizz
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid