Multithreading Problem While Checking Count List Property

I have a list of newJobs . Some threads add items to this list, and another thread removes items from it if it is not empty. I have ManualResetEvent newJobEvent , which is set when items are added to the list, and reset when items are removed from it:

Adding items to the list is as follows:

lock(syncLock){ newJobs.Add(job); } newJobEvent.Set(); 

Deleting tasks is performed as follows:

 if (newJobs.Count==0) newJobEvent.WaitOne(); lock(syncLock){ job = newJobs.First(); newJobs.Remove(job); /*do some processing*/ } newJobEvent.Reset(); 

When the line

 job=newJobs.First() 
Performed

. Sometimes I get an exception that the list is empty. I assume the check is:

  if (newJobs.Count==0) newJobEvent.WaitOne(); 

should also be in the lock statement, but I'm afraid of dead ends on the line newJobEvent.WaitOne ();

How can I solve it?

Thank you very much and sorry for the long post!

+4
source share
3 answers

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(); } } } 
+2
source

Without answering your question, but if you are using the .NET framework 4, you can use the new ConcurrentQueue , which makes all the locks for you.

Regarding your question:

One of the scenarios that may occur when such a problem occurs is the following:

  • The newJob.Add in thread enters the lock, calls newJob.Add , leaves the lock.
  • Context switch to delete stream. He checks the void, sees the element, enters the locked area, deletes the element, discards an event that has not yet been set.
  • Switching the context back to the insert stream, the event is set.
  • The context will return to the delete stream. He checks the void, does not see objects, waits for an event - which is already installed, tries to get the first element ... Bang!

Set and reset the event inside the castle, and everything will be fine.

+1
source

I do not understand why deleting an object in the case of null objects should wait for the addition and deletion. It seems like this is against the logic.

0
source

All Articles