You're right. Calling WaitOne inside the lock may cause deadlock. And a check to make sure the list is empty should be done inside the lock, otherwise there might be a race with another thread trying to delete the item. Now your code looks suspiciously like a producer-consumer pattern, which is usually implemented with a blocking queue. If you are using .NET 4.0, you can use the BlockingCollection class.
However, let me go through a few ways that you can do this yourself. The first uses a List and a ManualResetEvent to demonstrate how this can be done using the data structures in your question. Note the use of the while in the Take method.
public class BlockingJobsCollection { private List<Job> m_List = new List<Job>(); private ManualResetEvent m_Signal = new ManualResetEvent(false); public void Add(Job item) { lock (m_List) { m_List.Add(item); m_Signal.Set(); } } public Job Take() { while (true) { lock (m_List) { if (m_List.Count > 0) { Job item = m_List.First(); m_List.Remove(item); if (m_List.Count == 0) { m_Signal.Reset(); } return item; } } m_Signal.WaitOne(); } } }
But thatβs not how I do it. I would like to move on to a simpler solution below using Monitor.Wait and Monitor.Pulse . Monitor.Wait is useful because it can be called inside the castle. In fact, it should be done that way.
public class BlockingJobsCollection { private Queue<Job> m_Queue = new Queue<Job>(); public void Add(Job item) { lock (m_Queue) { m_Queue.Enqueue(item); Monitor.Pulse(m_Queue); } } public Job Take() { lock (m_Queue) { while (m_Queue.Count == 0) { Monitor.Wait(m_Queue); } return m_Queue.Dequeue(); } } }
source share