如何让线程等待,直到变量达到 .NET/Csharp 中的一组值之一

发布于 2024-12-10 16:57:59 字数 556 浏览 0 评论 0原文

我编写了一个类,该类具有 TProperty 类型的属性和 Action 类型的事件,每当该属性发生更改时就会触发该事件。 TProperty 通常是枚举类型,但这并不重要。

我想创建一个带有签名的方法

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

,该方法会阻止调用它的线程,直到属性更改为 AllowedValues 中的值。当然,如果在属性已经是AllowedValues之一时调用WaitUntilPropertyIs,则不应出现阻塞。 WaitUntilPropertyIs 最多应等待 TimeoutMs 并在超过超时时返回 false(正常的 AutoResetEvent.Wait 语义)。

我愿意使用反应式扩展或传统的同步结构。

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

单挑你×的.吻 2024-12-17 16:57:59

对于这种情况,Rx 就显得有些过分了。您可以通过 ManualResetEventManualResetEventSlim 来完成此操作。您可以将您的解决方案设计为以下内容:

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;
    }
}

但我不知道您的确切要求,因此请考虑修改上面的代码以防止竞争条件。

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.

云裳 2024-12-17 16:57:59

在 Rx + ReactiveUI 中,这是通过以下方式完成的:

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

如果我们想添加一个超时 + 一个 bool 来表示该属性已实际设置,我们可以这样做:

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

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();
你与昨日 2024-12-17 16:57:59

我决定将所有这些功能包装在一个类中,以便我可以在其他地方重用它。这段代码可以解决问题。

在构造函数中,传入 CurrentValueFunc,它必须根据需要返回监视变量的当前值,传入 IsValueAcceptableFunc,如果当前值可接受,它必须返回 true。

您需要确保每当值更改时都会调用 ValueWatcher.ValueUpdated。

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
}

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
}
腹黑女流氓 2024-12-17 16:57:59

一个“Waiter”类和一个列表样式的容器应该可以做到这一点。使用相同的关键部分锁定列表访问和属性设置器/事件/任何内容。服务员类只需要AllowedValues 和一个供调用者等待的事件(好吧,也许是一个“bool check(IEnumerable newAllowedValues)”帮助程序)。

应该是这样。在WaitUntilPropertyIs()中,获取CS并首先检查属性值,看看是否可以释放CS并立即返回,如果不能,调用者必须等待。创建一个服务员,复制传递的AllowedValues,将服务员添加到列表中,释放CS并在传递的超时时间内等待事件。当事件等待返回时,重新获取 CS,从列表中删除服务员,对其进行 dispose(),释放 CS 并从事件等待调用中返回 true/false。

在setter/Action/其他中,获取CS,迭代列表,与新的AllowedValues进行比较,在新值满足范围的任何服务员上触发任何事件,然后释放CS。

平均值,
马丁

A 'Waiter' class and a list-style container should do it. Lock up the list access and the property setter/event/whatever with the same Critical Section. The waiter class needs only the AllowedValues and an event for the callers to wait on, (OK, maybe a 'bool check(IEnumerable newAllowedValues)' helper).

That should be it. In WaitUntilPropertyIs(), acquire the CS and check against the property values first to see if it can just release the CS and return immediately, if not, the caller has to wait. Create a waiter, copy in the passed AllowedValues, add the waiter to the list, release the CS and wait on the event with the passed timeout. When the event wait returns, re-acquire the CS, remove the waiter from the list, dispose() it, release the CS and return with true/false from the event wait call.

In the setter/Action/whatever, acquire the CS, iterate the list, comparing with the new AllowedValues, fire any event on any waiter where the new values satisfy the range and then release the CS.

Rgds,
Martin

救星 2024-12-17 16:57:59

将该属性建模为Behavior(来自 Rx 的类,以及响应式编程中针对在一段时间内发生变化的值的一般概念)。

Model that property as Behavior (class from Rx, and a general concept in reactive programming for values that changes over a period of time).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文