I'm having some issues with thread synchronization in C#. I have a shared object which gets manipulated by two threads, I've made access to the object mutually exclusive using lock(), but I also want to block each thread depending on the state of the shared object. Specially block thread A when the object is empty, block thread B when the object is full, and have the other thread signal the blocked thread when the object state changes.
I tried doing this with a ManualResetEvent, but have run into a race condition where thread B will detect the object is full, move to WaitOne, and thread A will come in and empty the object (signalling the MRE every access, and block itself once the object is empty) before thread A hits its WaitOne, meaning thread A is waiting for the thread to not be full, even though it isn't.
I figure that if I could call a function like 'SignalAndWaitOne', that would atomically signal before waiting, it would prevent that race condition?
Thanks!
A typical way to do this is to use Monitor.Enter, Monitor.Wait and Monitor.Pulse to control access to the shared queue. A 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
A BlockingCollection
is already provided by .NET 4.0.
If you're on an earlier version, then you can use the Monitor
class directly.
EDIT: The following code is totally untested, and does not handle maxCount
values that are small (<= 2). It also doesn't have any provisions for timeouts or cancellation:
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);
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With