For some situation, we need to implement a FIFO execution model using multiple threads, like the following watchdog scenario.
A postman delivers books to the door from time to time. A puppy (watchdog) is at home, and he will gladly grab all the books that delivered at the door and run away into the back yard to do some work with the books, in a sequential order as the books are arrived. While the puppy is working on the books at the back yard, the postman may or may not deliver some more books. After the puppy work is done in the yard, the puppy will run back to the front door to see if the postman has already delivered some more books or not. If not, the puppy will just wait at the front door for the postman, else the puppy will grab all the books available at the door and repeat.
So as easily as you can figure out, the postman is the producer thread, the puppy is the consumer thread. There can be multiple postmen (multiple producer threads) to deliver the books at the door, however there will be only one puppy (consumer thread) because we want to make sure that an item (a book) can only be worked on after a previous item has been finished worked on.
This MSDN article has provided a great way to do this: http://msdn.microsoft.com/en-us/magazine/dd419664.aspx. The article provides two models: first with a dedicated thread, which will block if no work items are available; the second model is my favorite, it utilizes the thread pool, and it will not block if there is no work items are available (after all, what's the point of blocking if there is no work?), and therefore save us a thread.
Here is the code for the second model.
Code
using System;
using System.Collections.Generic;
using System.Threading;
namespace TestWatchDog
{
internal class WorkItem
{
public WaitCallback Callback;
public object State;
public ExecutionContext Context;
private static ContextCallback _contextCallback = s =>
{
var item = (WorkItem)s;
item.Callback(item.State);
};
public void Execute()
{
if (Context != null)
ExecutionContext.Run(Context, _contextCallback, this);
else Callback(State);
}
}
class FifoExecution
{
private Queue<WorkItem> _workItems = new Queue<WorkItem>();
private bool _delegateQueuedOrRunning = false;
public void QueueUserWorkItem(WaitCallback callback, object state)
{
var item = new WorkItem
{
Callback = callback,
State = state,
Context = ExecutionContext.Capture()
};
lock (_workItems)
{
_workItems.Enqueue(item);
if (!_delegateQueuedOrRunning)
{
_delegateQueuedOrRunning = true;
ThreadPool.UnsafeQueueUserWorkItem(ProcessQueuedItems, null);
}
}
}
using while loop#region using while loop
private void ProcessQueuedItems(object ignored)
{
while (true)
{
WorkItem item;
lock (_workItems)
{
if (_workItems.Count == 0)
{
_delegateQueuedOrRunning = false;
Console.WriteLine("finished looping");
break;
}
item = _workItems.Dequeue();
}
try { item.Execute(); }
catch
{
ThreadPool.UnsafeQueueUserWorkItem(ProcessQueuedItems, null);
throw;
}
}
}
#endregion
recursive#region recursive
//private void ProcessQueuedItems(object ignored)
//{
// WorkItem item;
// lock (_workItems)
// {
// if (_workItems.Count == 0)
// {
// _delegateQueuedOrRunning = false;
// return;
// }
// item = _workItems.Dequeue();
// }
// try { item.Execute(); }
// finally
// {
// ThreadPool.UnsafeQueueUserWorkItem(ProcessQueuedItems,
// null);
// }
//}
#endregion
}
class TestWatchDog
{
static void Main(string[] args)
{
var watchdogSample = new FifoExecution();
watchdogSample.QueueUserWorkItem(item =>
{
Console.WriteLine("WatchDog - ThreadID {0} is working on {1}", Thread.CurrentThread.ManagedThreadId, item);
}, "Gone with the wind");
Thread.SpinWait(1000000000);
watchdogSample.QueueUserWorkItem(item =>
{
Console.WriteLine("WatchDog - ThreadID {0} is working on {1}", Thread.CurrentThread.ManagedThreadId, item);
}, "Give me a bone");
watchdogSample.QueueUserWorkItem(item =>
{
Console.WriteLine("WatchDog - ThreadID {0} is working on {1}", Thread.CurrentThread.ManagedThreadId, item);
}, "Give me another bone");
watchdogSample.QueueUserWorkItem(item =>
{
Console.WriteLine("WatchDog - ThreadID {0} is working on {1}", Thread.CurrentThread.ManagedThreadId, item);
}, "CLR via C#");
Console.ReadLine();
}
}
}
If I were to implement it, I probably will do something like the one in this article, which is more intuitive, or in a more traditional way, http://www.geekscafe.net/post/Blocking-Queues-Threade28099s-Communication-in-C.aspx