问题
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..
回答1:
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..
回答2:
Any reason not to use a ManualResetEvent
? That won't reset itself when one waiting thread has passed, so they'll all be released.
Of course, it means that if you need to Reset
the event after all the waiting threads have gone through, you'd need some way of detecting that. You could possibly use a Semaphore
instead, but I suspect it would be complicated.
Do you need to reset the event immediatley after you've set it, in your case?
回答3:
I came up with a different solution to this problem.
It assumes the threads waiting for the event are not tight loops on the WaitOne()
calls, and there's some work between the wait calls.
It uses one AutoResetEvent
, calls WaitOne(0)
and Set()
continuously until no other threads waits for the event.
// the only event we'll use:
AutoResetEvent are = new AutoResetEvent(false);
// starting threads:
for (int i = 0; i < 10; i++)
{
string name = "T" + i;
new Thread(() => { while (true) { are.WaitOne(); WriteLine(name); } }).Start();
}
// release all threads and continue:
while (!are.WaitOne(0))
are.Set();
The above code is tested for 1000 threads, it did released them all (though there's an overhead of too many iteration on the while loop, which can be easily limited to zero when there's a bit more work between the wait calls in the threads.
Something that is not clear to me from the documentation is whether it is possible for Set() to release a WaitOne() that was called later on the same thread - if this situation is possible then this solution is not safe to use since it might not release all the threads before exiting the while loop. It would be nice if someone could shed some light on it.
来源:https://stackoverflow.com/questions/5416270/multiple-threads-waiting-on-one-event