Multiple threads waiting on one event?
Asked Answered
C

3

5

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..

Catboat answered 24/3, 2011 at 7:58 Comment(0)
C
4

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..

Catboat answered 24/3, 2011 at 9:56 Comment(0)
J
3

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?

Jellybean answered 24/3, 2011 at 8:2 Comment(4)
In the actual use case, I have a few different functions that'll block, waiting for the state to get to what they want. ie a WaitForBlob, and a WaitForSusan, among other things. What I want is so that whenever the global state changes, all threads waiting re-evaluate their condition to see if they're now good to go. I haven't quite figured out how I could bend ManualResetEvents to do that.Catboat
For more info, it's come about as a result of dabbling in Eric Lippert's immutability in C#. I have an immutable AVLTree storing a heap of data. Sometimes a portion of it gets "locked", as I wait to read it from a device. Now if I want to retrieve this "locked" segment, it has to wait for the response before it can return the latest value. So what I need to do is wait for the tree to be changed, and if that portion is still locked, continue waiting until it isn't. Multiple threads need to be able to do this cleanly, preferably without spinning/small sleeps.Catboat
@Mania: If you've got code using AutoResetEvent, ManualResetEvent is exactly the same - it just doesn't reset itself when the first thread manages to wait. Another option you might think about is using events, where each event handler would Pulse a monitor that the appropriate thread has called Wait on. Having said that, using low level concepts like this sounds like a bad idea in some ways... are you using .NET 4? If so, something in the TPL may well help you.Jellybean
Sadly, 3.5. I have figured out a way I can go about it using ManualResetEvents, but it's not too pretty. Currently reading up on Monitor.Pulse/Wait, but they seem to be a bit too low-level as you say. I think I'm going to make the proposed "MultiEventHandle" class using ManualResetEvents to hide the logic. Thanks for your help.Catboat
S
1

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.

Slat answered 13/8, 2017 at 16:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.