This is an example on how to create a thread queue. In this example we will basic a basic class that can be reused to process a queue of items in a background thread.

The first thing I should probably point out is that the .net framework does not have an IRunnable interface like java does. I am not sure why this is or at least I have never found something like it. So we will need to create an IRunnable interface this is so that any sort of object passed to the class can be runnable by the queue processor. As any object passed will be required to be implement the same Run function. Code is below for the interface.

public interface IRunnable
{
        void Run();
}

We are also going to add a number of functions.

  • Add - To add an item to the queue
  • Run - To process the queue and execute the IRunnable interface function.
  • Error event. Fired if when an item throws and exception.
  • Complete event. First then an item finished executing.
  • Count property so the outside world can see how many items are queued.
  • Maximum length property so we can throw an error if too many items are added.

We will also add the correct locks to prevent the background thread and the normal application thread from trying to access and modify any object's at the same times. This typically caused an issues and headaches in multi threaded
programming. The purpose of encapsulating these into the class from the outside world is to limit the scope of locks so that some of the more nasty difficult bugs occur. If you don't have access to a variable you can't lock it!.

First lets keep it simple. We only need the functions Add and Run to demonstrate
how this works. It probably also worth pointing out that with lock's we only
want to hold the lock for a short period of time possible.

public class ThreadQueue : ThreadSimple
{
        private Queue<IRunnable> Q = new Queue<IRunnable>();

        public ThreadQueue()
        {
                Start();
        }

        public void Add(IRunnable obj)
        {
                lock (Q)
                {
                        Q.Enqueue(obj);
                }
        }

        protected override void Run(object obj)
        {
                while (true)
                {
                        try
                        {
                                IRunnable item = null;

                                lock (Q)
                                {
                                        if (Q.Count > 0)
                                                item = Q.Dequeue();
                                }
                                if (item == null)
                                {
                                        Thread.Sleep(100);
                                        continue;       //Restart the loop
                                }

                                item.Run();
                        }
                        catch (Exception ex)
                        {
                                throw (ex);
                        }
                }
        }
}

The above example is quite simple. Did you notice the problem though? The sleep should not exist. There is a better way for getting a background thread the sleep until something is added to the queue. We can do this using the class called Monitor. The idea behind this is to send a pulse to anything that is "sleeping" on the object to wake it up. The other thread will then wake up and start processing the items.

The technical problem with this is normally getting to sleep. The idea is you lock the object then check the state. If the state is such that you need to sleep since there is no work to do then you sleep until somebody changed the state and notifies you. The Monitor.Wait is a special function that will allow you to sleep and unlock the object so that something else can acquire a lock. The lock is then required when the thread wakes.

Our code will turn into this after the changes.

public void Add(IRunnable obj)
{
        lock (Q)
        {
                Q.Enqueue(obj);
                Monitor.Pulse(Q);
        }
}

protected override void Run(object obj)
{
        while (true)
        {
                try
                {
                        IRunnable item = null;

                        lock (Q)
                        {
                                if (Q.Count == 0)
                                        Monitor.Wait(Q);

                                if (Q.Count > 0)
                                        item = Q.Dequeue();
                        }
                        if (item == null)
                                continue;       //Restart the loop

                        item.Run();
                }
                catch (Exception ex)
                {
                        throw (ex);
                }
        }
}

I am going to leave the the exercise up the the end reader on implementing the other minor functions for queue length / maximum and events for error's and the when an item is completed. It is also possible to notify from the IRunnable
object passed when it has finished executing.

What you may not have noticed is that there is an issue about how to destroy the class. Since the thread in the background is still running. This really comes down to a design decision depending on your applications. It may be effective to never exist. However let's consider it is required. Another choice may be to stop adding things to the thread wait until the queue is empty then call the Stop function to kill the background thread.

Another possible option that I have used in the past is the make the ThreadQueue support the Dispose method and also inherit the IDisposeable interface (the standard way to do it in .net). You could then have this function call the stop or an abort (see Thread.Abort) to kill the background thread. Raise an error event on all remaining items on the queue.





Last Modified: 18 February 2017

Releated Posts


2011-10-27 - CSharp - Number Of Cores and Processors
2011-10-04 - CSharp - Thread Queue Example
2011-09-29 - CSharp - Background Thread / Task
2011-09-17 - CSharp - Lookup Hostname
2011-08-29 - CSharp - IsGuid
2011-08-29 - C Sharp - StopWatch, The high resolution timer
2011-08-26 - CSharp - Convert Between Meters and Feet
2011-08-23 - CSharp - ThreadPool and Windows Forms
2011-08-10 - C Sharp - MD5Sum / SHASum
2011-07-22 - CSharp - Fibonacci sum
2011-07-13 - CSharp - Palindrome
2011-07-11 - CSharp - The string Reverse
2011-07-10 - CSharp - Interviews FizzBuzz
2011-06-27 - CSharp - HowTo Parse a URL
2011-06-22 - CSharp - Extending any class
2011-06-13 - ASPNET - Login by username or email
2011-05-12 - CSharp - ASPNET Logoff
2011-03-07 - C Sharp - Windows Logoff
2011-03-02 - ASPNET - Dynamic Controls
2011-03-01 - C Sharp - Resize an image by size of height
2011-02-23 - C Sharp / MSSQL Get inserted value of NEWSEQUENTIALID()
2011-02-10 - CSharp - Shutdown or reboot windows
2011-02-10 - CSharp - Wake on Lan (WOL) Packet
2011-02-06 - C Sharp - Gridview Bound Yes No
2011-02-05 - MSSQL - Levenshtein
2011-01-30 - MSSQL - RegEx Support
2011-01-30 - MSSQL - Adding SHASum support
2011-01-30 - MSSQL - Enabling CLR Functions
2011-01-30 - MSSQL - Adding MD5 checksum support