How to make a thread wait until a variable reaches one of a set of values in .NET/Csharp

13,256

Solution 1

Rx is overkill for this kind of scenario. You can do it via ManualResetEvent or ManualResetEventSlim. You can pattern your solution to the following:

public event Action<TProperty> MyEvent;
public TProperty Prop { get; private set; }

bool WaitUntilPropertyIs(int timeout, IEnumerable<TProperty> allowedValues)
{
    var gotAllowed = new ManualResetEventSlim(false);
    Action<int> handler = item =>
    {
        if (allowedValues.Contains(item)) gotAllowed.Set();
    };

    try
    {
        MyEvent += handler;
        return allowedValues.Contains(Prop) || gotAllowed.Wait(timeout);
    }
    finally
    {
        MyEvent -= handler;
    }
}

I don't know your exact requirements though so consider modifying the code above to protect against race conditions.

Solution 2

In Rx + ReactiveUI, this is accomplished via:

someObject.ObservableForProperty(x => x.SomeProperty)
    .Where(x => x.Value == "Something")
    .First();

If we wanted to add a timeout + a bool signifying that the property is actually set, we could do this:

bool valueIsSet = someObject.ObservableForProperty(x => x.SomeProperty)
    .Where(x => x.Value == "Something")
    .Select(x => true)
    .Timeout(TimeSpan.FromSeconds(5), Observable.Return(false))
    .First();

Solution 3

I decided to wrap all of this functionality in a class so I can reuse it in other places. This code does the trick.

On the constructor, pass in CurrentValueFunc which must return the current value of the watched variable on demand, pass in IsValueAcceptableFunc which must return true if the current value is acceptable.

You need to ensure that ValueWatcher.ValueUpdated is called whenever the value changes.

public class ValueWatcher<TValue> : IDisposable
{
    ManualResetEvent _ev = new ManualResetEvent(false);
    Func<TValue, bool> _isValueAcceptableFunc;

    public ValueWatcher(Func<TValue> CurrentValueFunc, Func<TValue, bool> IsValueAcceptableFunc)
    {
        _isValueAcceptableFunc = IsValueAcceptableFunc;
        ValueUpdated(CurrentValueFunc.Invoke());
    }

    public void ValueUpdated(TValue Value)
    {
        if (_isValueAcceptableFunc.Invoke(Value))
            _ev.Set();
        else
            _ev.Reset();
    }

    public bool Wait()
    {
        return _ev.WaitOne();
    }

    public bool Wait(int TimeoutMs)
    {
        return _ev.WaitOne(TimeoutMs);
    }

    public bool Wait(TimeSpan ts)
    {
        return _ev.WaitOne(ts);
    }

    #region IDisposable Members

    public void Dispose()
    {
        Dispose(true);
    }

    void Dispose(bool Disposing)
    {
        if (Disposing)
        {
            _ev.Dispose();
        }
    }

    #endregion
}
Share:
13,256
sevzas
Author by

sevzas

Updated on June 05, 2022

Comments

  • sevzas
    sevzas almost 2 years

    I've written a class that has a property of type TProperty and an event of type Action<TProperty> that fires whenever that property changes. TProperty is typically an enum type, but that shouldn't matter.

    I'd like to create a method with signature

    bool WaitUntilPropertyIs(int TimeoutMs, IEnumerable<TProperty> AllowedValues) 
    

    that blocks the thread that called it until the property changes to a value that is in AllowedValues. Ofcourse if WaitUntilPropertyIs is called when the property is alread one of the AllowedValues, there should be no blocking. WaitUntilPropertyIs should wait at most TimeoutMs and return false if the Timeout is exceeded (normal AutoResetEvent.Wait semantics).

    I'm open to using Reactive Extensions or traditional synchronization constructs.