What (I think) I want is the equivelant of an AutoResetEvent
that multiple threads can wait on, all to be resumed when it's set.
I know this can be achieved by having one AutoResetEvent
per thread and setting each of them - but is there an easier way? A way that doesn't depend on arrays of eventhandles?
Effectively what (I think) I'd like is to be able to do this:
private volatile string state;
private MultiEventHandle stateChanged = new MultiEventHandle();
public void WaitForBlob()
{
while (true)
{
object saved = stateChanged.Current; // some sentinel value
if (state == "Blob") break;
stateChanged.WaitTilNot(saved); // wait til sentinel value != "current"
}
}
public void SetBlob()
{
state = "Blob";
stateChanged.Change(); // stateChanged.Current becomes a new sentinel object
}
ie, any number of threads can call WaitForBlob
, and at any time (no race conditions) SetBlob
can be called by yet another thread, and all waiting threads will detect the change immediately - and importantly, with no spin locks or Threading.Sleeps.
Now I think I can implement a "MultiEventHandle
" relatively easily. But my question is... is there a better way? Surely I'm going about this wrong as it must be a pretty common use case, but I can't seem to find an in-built tool for the job. I'm afraid I may be going about inventing a square wheel here..
I've wrapped up a possible solution into a "WatchedVariable" class using Monitor.PulseAll/Wait behind the scenes (learning a bit about the Monitor class in the process). Posting here in case anyone else ever runs into the same problem - may be of some use with immutable data structures. Thanks to Jon Skeet for assistance.
Usage:
private WatchedVariable<string> state;
public void WaitForBlob()
{
string value = state.Value;
while (value != "Blob")
{
value = state.WaitForChange(value);
}
}
Implementation:
public class WatchedVariable<T>
where T : class
{
private volatile T value;
private object valueLock = new object();
public T Value
{
get { return value; }
set
{
lock (valueLock)
{
this.value = value;
Monitor.PulseAll(valueLock); // all waiting threads will resume once we release valueLock
}
}
}
public T WaitForChange(T fromValue)
{
lock (valueLock)
{
while (true)
{
T nextValue = value;
if (nextValue != fromValue) return nextValue; // no race condition here: PulseAll can only be reached once we hit Wait()
Monitor.Wait(valueLock); // wait for a changed pulse
}
}
}
public WatchedVariable(T initValue)
{
value = initValue;
}
}
Whilst it's passed my test cases, use at your own risk.
Now to consult meta to figure out which answer I'm supposed to accept..
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