Click here to Skip to main content
11,505,486 members (70,195 online)
Click here to Skip to main content

Simple Way to Structure Threads for Control

, 1 Aug 2013 CPOL 7.8K 13
Rate this:
Please Sign up or sign in to vote.
I'd like to share the pattern I commonly use when creating threads in C# and discuss some of the highlights.

Background

I've previously discussed the differences between the BackgroundWorker and Thread classes, but I figured it would be useful to touch on some code. I'd like to share the pattern I commonly use when creating threads in C# and discuss some of the highlights.

The Single Thread

I like to use this design when I have a single thread I need to run and in the context of my object responsible for running the thread, I do mean having a single thread. Of course, you could have your object in control of multiple threads as long as you repeat this design pattern for each of them.

Here's the interface that I'll be using for all of the examples:

internal interface IThreadRunner
{
        #region Exposed Members

        void Start();

        void Stop();

        #endregion 
} 

Behold!  The single thread runner! 

internal class SingleThreadRunner : IThreadRunner
{
    #region Fields

    private readonly object _threadLock;
    private readonly AutoResetEvent _trigger;
    private Thread _theOneThread;

    #endregion

    #region Constructors

    /// <summary>
    /// Prevents a default instance of the class from being created.
    /// </summary>
    private SingleThreadRunner()
    {
        _threadLock = new object();
        _trigger = new AutoResetEvent(false);
    }

    #endregion

    #region Exposed Members

    public static IThreadRunner Create()
    {
        return new SingleThreadRunner();
    }

    public void Start()
    {
        lock (_threadLock)
        {
            // check if already running
            if (_theOneThread != null)
            {
                return;
            }
 
            _theOneThread = new Thread(DoWork);
            _theOneThread.Name = "The One Thread";
            _theOneThread.Start(_trigger);
        }
    }

    public void Stop()
    {
        lock (_threadLock)
        {
            // check if not running
            if (_theOneThread == null)
            {
                return;
            }

            _theOneThread = null;
            _trigger.Set();
        }
    }

    #endregion

    #region Internal Members

    private void DoWork(object parameter)
    {
        var currentThread = Thread.CurrentThread;

        // this was the trigger that we passed in. elesewhere in the 
        // instance, we can use this object to wake up the thread.
        var trigger = (AutoResetEvent)parameter;
 
        try
        {
            // keep running while we're expected to be running
            while (currentThread == _theOneThread)
            {
                // DO ALL SORTS OF AWESOME WORK HERE.
                Console.WriteLine("Awesome work being done.");

                // put this thread to sleep, but remember it can be woken 
                // up from other places in this instance.
                trigger.WaitOne(1000);
            }
        }
        finally
        {
            lock (_threadLock)
            {
                // if we were still expected to be running, change the 
                // state to suggest that we're not
                if (_theOneThread == currentThread)
                {
                    _theOneThread = null;
                }
            }
        }
    }

    #endregion
}

This design was taken from some Java programming I had done in a previous life. Essentially, I have a thread that is responsible for doing some work in a loop. It could be anything... Periodically polling for some data, a work dequeing thread, a random-cursor-moving thread... Anything! The point is, you only want one of these suckers hanging around. How is this accomplished?

Leveraging the instance variable that marks the one expected running thread is key here. Whenever this thread checks if it should still be running, if the current thread doesn't match what's assigned to the instance variable then it needs to stop! This means you could potentially spawn off two of these threads, and if you set the instance variable to one of the two, then the other one should kill itself off! Pretty neat.

By using the reset event, we can actually interrupt this thread if it's sleeping. This is great if we have a thread that periodically wakes up to do some work but we want to stop it and have it stop fast. We simple set our instance variable for the thread to be null and then set this thread's reset event to ensure it get's woken up. Presto! It wakes up, checks the condition, and realizes it needs to exit the loop.

Simple.

The Handful of Threads

This design is almost identical to the single thread design above. I use it primarily when I want to have an object responsible for a bunch of threads that are turned on/off under the same conditions. The major difference between the two designs? In the single thread scenario, we check that our current thread is still set to be the one instance. In this design, we need all of our threads to be checking against the same state object which is not going to be a single thread instance.

Let's have a peek:

internal class GroupThreadRunner : IThreadRunner
{
    #region Fields

    private readonly object _threadLock;
    private readonly Dictionary<Thread, AutoResetEvent> _triggers;

    private bool _running;

    #endregion

    #region Constructors

    /// <summary>
    /// Prevents a default instance of the class from being created.
    /// </summary>
    private GroupThreadRunner()
    {
        _threadLock = new object();
        _triggers = new Dictionary<Thread, AutoResetEvent>();
    }

    #endregion

    #region Exposed Members

    public static IThreadRunner Create()
    {
        return new GroupThreadRunner();
    }

    public void Start()
    {
        lock (_threadLock)
        {
            // check if any are already running
            if (_triggers.Count > 0)
            {
                return;
            }

            _running = true;

            const int NUMBER_OF_THREADS = 4;
            for (int i = 0; i < NUMBER_OF_THREADS; ++i)
            {
                var thread = new Thread(DoWork);
                thread.Name = "Thread " + i;

                var trigger = new AutoResetEvent(false);
                _triggers[thread] = trigger;

                thread.Start(trigger);
            }
        }
    }

    public void Stop()
    {
        lock (_threadLock)
        {
            // check if not running
            if (_triggers.Count <= 0)
            {
                return;
            }

            _running = false;
            foreach (var trigger in _triggers.Values)
            {
                trigger.Set();
            }
        }
    }

    #endregion

    #region Internal Members

    private void DoWork(object parameter)
    {
        var currentThread = Thread.CurrentThread;

        // this was the trigger that we passed in. elesewhere in the
        // instance, we can use this object to wake up the thread.
        var trigger = (AutoResetEvent)parameter;

        try
        {
            // keep running while we're expected to be running
            while (_running)
            {
                // DO ALL SORTS OF AWESOME WORK HERE.
                Console.WriteLine("Awesome work being done by " + currentThread.Name);

                // put this thread to sleep, but remember it can be woken
                // up from other places in this instance.
                trigger.WaitOne(1000);
            }
        }
        finally
        {
            lock (_threadLock)
            {
                _triggers.Remove(currentThread);

                // if we were still expected to be running, change the
                // state to suggest that we're not
                if (_running && _triggers.Count <= 0)
                {
                    _running = false;
                }
            }
        }
    }

    #endregion
}

Summary

The above patterns I discussed cover my common usage for threads: Instances that have reoccurring work over long periods of time. Both patterns are very similar and only have slight modifications to make them support one instance or many thread instances running. If you have one unique thread or many threads... there's a pattern for you!

Check out a full working example of this code over here:

License

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

Share

About the Author

Nick Cosentino
Team Leader Magnet Forensics
Canada Canada
I graduated from the University of Waterloo for Computer Engineering and I'm fortunate enough to work as a Team Lead of Software Engineering at Magnet Forensics. As a team lead, I'm often looking to encourage better coding standards, creative approaches to problem solving, and ensure that good clean code makes it into the code base. I want my team to produce top-notch code, but I want to make sure that we're all learning to become better developers along the way.

Blog: http://www.devleader.ca
Facebook: https://www.facebook.com/DevLeaderCa
LinkedIn: http://www.linkedin.com/in/nickcosentino
Twitter: http://www.twitter.com/nbcosentino
Google+: https://plus.google.com/+DevleaderCa/posts
Follow on   Twitter   Google+   LinkedIn

Comments and Discussions

 
GeneralInformation Pin
Plamen Kovandjiev13-Aug-13 23:32
memberPlamen Kovandjiev13-Aug-13 23:32 
GeneralRe: Information Pin
Nick Cosentino14-Aug-13 1:17
memberNick Cosentino14-Aug-13 1:17 
QuestionMessage Removed Pin
Arash M. Dehghani31-Jul-13 10:29
memberArash M. Dehghani31-Jul-13 10:29 
AnswerRe: Stoped?! Pin
Nick Cosentino31-Jul-13 10:43
memberNick Cosentino31-Jul-13 10:43 
GeneralMessage Removed Pin
Arash M. Dehghani31-Jul-13 11:07
memberArash M. Dehghani31-Jul-13 11:07 
GeneralRe: Stoped?! Pin
Nick Cosentino31-Jul-13 11:14
memberNick Cosentino31-Jul-13 11:14 
GeneralMessage Removed Pin
Arash M. Dehghani31-Jul-13 20:09
memberArash M. Dehghani31-Jul-13 20:09 
GeneralRe: Stoped?! Pin
Nick Cosentino1-Aug-13 1:22
memberNick Cosentino1-Aug-13 1:22 
GeneralMessage Removed Pin
Arash M. Dehghani1-Aug-13 2:01
memberArash M. Dehghani1-Aug-13 2:01 
GeneralRe: Stoped?! Pin
Nick Cosentino1-Aug-13 2:18
memberNick Cosentino1-Aug-13 2:18 
GeneralMessage Removed Pin
Arash M. Dehghani1-Aug-13 3:55
memberArash M. Dehghani1-Aug-13 3:55 
GeneralRe: Stoped?! Pin
Nick Cosentino1-Aug-13 4:11
memberNick Cosentino1-Aug-13 4:11 

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 | Terms of Use | Mobile
Web03 | 2.8.150520.1 | Last Updated 1 Aug 2013
Article Copyright 2013 by Nick Cosentino
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid