Posted by: Spenceee | January 6, 2010

Encapsulating threads within an object

I ran into an issue regarding encapsulation of one or more threads within an object. Basically I had refactored out a set of cross cutting concern logic into nice clean classes which had good coherency and made sense, storing only state appropriate to the function provided. There were minimal requirements for communication between these classes, with each object having a polling function (talking to non event driven hardware) which was called on a regular basis. Rather than have the code new the object, kick off a thread and manage it by looping and calling a polling function, I decided to encapsulate the functionality into the class itself.

So I have a Poller class where instantiating it causes a thread to start and launch into the object’s private polling function. This class is then derived to create an object that exposes threadsafe public methods for driving the behaviour of the class, with the disposer causing the thread to stop.

This means that the functionality exposed by the class is as easy to use as a set of instance functions and the class itself can be wrapped into a using statement to manage the thread lifetimes. No code exists at the caller to manage the objects threading or synchronisation. Thus I believe that this code when used in appropriate situations could simpler to test, simpler to maintain and simpler to use. I guess you have to trade off control for simplicity.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace Spenceee
{
    public abstract class Poller : IDisposable
    {
        protected Thread m_Thread;
        protected ManualResetEvent m_Stopper = new ManualResetEvent(false);
        /// <summary>
        /// The amount of time that the disposer will wait for the method
        /// to exit cleanly.
        /// </summary>
        private TimeSpan m_ThreadTimeout = new TimeSpan(0, 1, 0);

        /// <summary>
        /// Polling interval.
        /// </summary>
        private TimeSpan m_PollInterval;

        /// <summary>
        ///
        /// </summary>
        /// <param name="threadName">A friendly name for the objects thread.
        ///</param>
        /// <param name="state">Any information needed for the thread to
        /// start.</param>
        protected Poller(string threadName,
                TimeSpan pollInterval, object state)
        {
            m_PollInterval = pollInterval;
            ParameterizedThreadStart thrdStrt =
                    new ParameterizedThreadStart(threadMain);
            m_Thread = new Thread(thrdStrt);
            m_Thread.Name = threadName;
            //in case object isn't disposed, thread will not keep app alive.
            m_Thread.IsBackground = true;
            m_Thread.Start(state);
        }

        private void threadMain(object state)
        {
            Setup(state);
            while (!m_Stopper.WaitOne(m_PollInterval))
            {
                PollFunction();
            }
        }

        /// <summary>
        /// This function runs before the thread starts polling.
        /// </summary>
        /// <param name="state">Any needed parameters passed in via the
        /// constructor.</param>
        protected virtual void Setup(object state)
        {
        }

        /// <summary>
        /// This method is the polling function.
        /// </summary>

        protected abstract void PollFunction();
        #region IDisposable Members
        private bool disposed = false;

        /// <summary>
        /// This implementation is not threadsafe
        /// </summary>
        public void Dispose()
        {
            if (!disposed)
            {
                disposed = true;
                Cleanup();
                GC.SuppressFinalize(this);
            }
        }
        #endregion

        ~Poller()
        {
            CleanUp();
        }

        private void CleanUp()
        {
            m_Stopper.Set();
            if (!m_Thread.Join(m_ThreadTimeout))
            {
                m_Thread.Abort();
            }
        }

    }
}

An example implementation of this class is a counter, where you can increment a counter from any thread and it will write the value to console every second.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace Spenceee
{
    class CountDisplayer : Poller
    {
        long count = 0;
        public CountDisplayer()
            : base("Count Displayer", new TimeSpan(0, 0, 1), null)
        {
        }

        protected override void PollFunction()
        {
            long value = Interlocked.Read(ref count);
            Console.WriteLine(String.Format("Counter is now: {0}", value));
        }

        ///<summary>
        ///This method increments the count displayer's counter by one.
        ///</summary>
        public void IncrementCount()
        {
            Interlocked.Increment(ref count);
        }
    }
}

As you can see the actual implementation of the poller is now significantly easier and the only code you can see relates to what you class should be doing, not the how of the thread starting up etc. Additionally the thread destruction is encapsulated using IDisposable, meaning that you can implement the using() feature to guarantee the lifetime of the object.

namespace Spenceee
{
    class Program
    {
        static void Main(string[] args)
        {
            using (CountDisplayer dsp = new CountDisplayer())
            {
                dsp.IncrementCount();
                dsp.IncrementCount();
                Console.WriteLine("Sleeping");
                Thread.Sleep(5000);
                dsp.IncrementCount();
                dsp.IncrementCount();
                Console.WriteLine("Sleeping");
                Thread.Sleep(2000);
            }
        }
    }
}

Possible improvements that could be made include utilising the threadpool on a timer callback for any polling actions in lieu of a blocked thread.

Any feedback is welcome!

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Categories

%d bloggers like this: