This post originated from an RSS feed registered with .NET Buzz
by Udi Dahan.
Original Post: Update of ThreadSafeQueue - don't multi-thread without it
Feed Title: Udi Dahan - The Software Simplist
Feed URL: http://feeds.feedburner.com/UdiDahan-TheSoftwareSimplist
Feed Description: I am a software simplist. I make this beast of architecting, analysing, designing, developing, testing, managing, deploying software systems simple.
This blog is about how I do it.
[TestFixture]
public class QueueTests
{
private IQueue q;
private ManualResetEvent flag;
[SetUp]
public void Setup()
{
q = new ThreadSafeQueue();
flag = new ManualResetEvent(false);
}
[TearDown]
public void Teardown()
{
flag.Close();
}
[Test]
public void EnqueueShouldWork()
{
q.Enqueue(new object());
}
[Test]
public void DequeueAfterEnqueueShouldReturnTheSameObject()
{
object o = new object();
q.Enqueue(o);
Assert.AreEqual(o, q.Dequeue());
}
#region DequeueOnAnEmptyQueueShouldSleep
[Test]
public void DequeueOnAnEmptyQueueShouldSleep()
{
Thread t = new Thread(new ThreadStart(DequeueFromEmptyQueue));
t.Start();
flag.WaitOne(); // let other thread run
Thread.Sleep(100);
Assert.AreEqual(ThreadState.WaitSleepJoin, t.ThreadState);
t.Abort(); // clean up
}
private void DequeueFromEmptyQueue()
{
flag.Set();
q.Dequeue();
}
#endregion
#region EnqueueShouldReleaseSleepingThread
[Test]
public void EnqueueShouldReleaseSleepingThread()
{
Thread t = new Thread(new ThreadStart(DequeueFromNonEmptyQueue));
t.Start();
flag.WaitOne(); // let other thread run
Thread.Sleep(100);
Assert.AreEqual(ThreadState.WaitSleepJoin, t.ThreadState);
flag.Reset();
q.Enqueue(1);
flag.WaitOne(); // let other thread finish
Thread.Sleep(100);
Assert.IsTrue(true);
}
private void DequeueFromNonEmptyQueue()
{
flag.Set();
object o = q.Dequeue();
flag.Set();
Assert.AreEqual(1, Convert.ToInt32(o));
}
#endregion
}
Then, the code:
public interface IQueue
{
void Enqueue(object o);
object Dequeue();
}
///
/// Summary description for ThreadSafeQueue.
///
public class ThreadSafeQueue: IQueue
{
private System.Collections.Queue q = new System.Collections.Queue();
private ManualResetEvent newItemEntered = new ManualResetEvent(false);
public Queue()
{
}
public void Enqueue(object o)
{
lock(this)
{
q.Enqueue(o);
newItemEntered.Set();
}
}
public object Dequeue()
{
newItemEntered.WaitOne();
lock(this)
{
object result = q.Dequeue();
if (q.Count == 0)
newItemEntered.Reset();
return result;
}
}
}
Think of multi-threaded applications as an architecture of cooperation services, each thread a service. These services will receive messages to perform work from a queue like the one above.
Threading is a design issue. The number of threads in a system, their responsibilities, what objects they can access, all these are design issues. Don't mess up your design by allowing a BeginInvoke here, or a Thread.Start there. Don't think that the ThreadPool is your friend. If you didn't knowingly design for a thread to begin execution, your that much closer to a deadlock.
The final bonus is performance. If no thread needs to actively wait for another thread, then all threads can work in parallel. Even better, this will scale up nicely with more processors. Better than that, scaling out is a piece of cake by swapping out the in-memory-queue implementation with an out-of-process one. Finally, each part of the system is single threaded. There's no reason to have to ponder reentrant business objects.
Oh, and don't multi-thread if you don't have to. :)