How to make a thread wait until a variable reaches one of a set of values in .NET/Csharp
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
}
sevzas
Updated on June 05, 2022Comments
-
sevzas almost 2 years
I've written a class that has a property of type
TProperty
and an event of typeAction<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 theAllowedValues
, there should be no blocking.WaitUntilPropertyIs
should wait at mostTimeoutMs
and return false if the Timeout is exceeded (normalAutoResetEvent.Wait
semantics).I'm open to using Reactive Extensions or traditional synchronization constructs.