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.
Did You find this page useful?
Yes
No