Can you signal and wait atomically with C # thread synchronization?

I'm having trouble synchronizing threads in C #. I have a shared object that is controlled by two threads, I made access to the object mutually exclusive with lock (), but I also want to block each thread depending on the state of the shared object. Specifically block thread A when the object is empty, block thread B when the object is full, and another thread signals a blocked thread when the state of the object changes.

I tried to do this using ManualResetEvent, but ran into a race condition when thread B detects that the object is full, go to WaitOne and thread A will enter and lower the object (signals MRE about every access and block itself when the object is empty) before than thread A gets into WaitOne, that is, thread A expects that thread will not be full, even if it is not.

I believe that if I could call a function like SignalAndWaitOne that would atomically signal before waiting, would this prevent this race condition?

Thanks!

+4
source share
2 answers

A typical way to do this is to use Monitor.Enter, Monitor.Wait, and Monitor.Pulse to control access to the shared queue. Sketch:

shared object sync = new object() shared Queue q = new Queue() Producer() Enter(sync) // This blocks until the lock is acquired while(true) while(q.IsFull) Wait(sync) // this releases the lock and blocks the thread // until the lock is acquired again // We have the lock and the queue is not full. q.Enqueue(something) Pulse(sync) // This puts the waiting consumer thread to the head of the list of // threads to be woken up when this thread releases the lock Consumer() Enter(sync) // This blocks until the lock is acquired while(true) while(q.IsEmpty) Wait(sync) // this releases the lock and blocks the thread // until the lock is acquired again // We have the lock and the queue is not empty. q.Dequeue() Pulse(sync) // This puts the waiting producer thread to the head of the list of // threads to be woken up when this thread releases the lock 
+7
source

A BlockingCollection already provided by .NET 4.0.

If you are using an earlier version, you can use Monitor directly.

EDIT: The following code is completely untested and does not process maxCount values ​​that are small (<= 2). It also lacks any provision for timeouts or cancellations:

 public sealed class BlockingList<T> { private readonly List<T> data; private readonly int maxCount; public BlockingList(int maxCount) { this.data = new List<T>(); this.maxCount = maxCount; } public void Add(T item) { lock (data) { // Wait until the collection is not full. while (data.Count == maxCount) Monitor.Wait(data); // Add our item. data.Add(item); // If the collection is no longer empty, signal waiting threads. if (data.Count == 1) Monitor.PulseAll(data); } } public T Remove() { lock (data) { // Wait until the collection is not empty. while (data.Count == 0) Monitor.Wait(data); // Remove our item. T ret = data.RemoveAt(data.Count - 1); // If the collection is no longer full, signal waiting threads. if (data.Count == maxCount - 1) Monitor.PulseAll(data); } } } 
+5
source

All Articles