Rx在一定时间后触发动作

时间:2014-10-17 20:42:35

标签: c# system.reactive

我有一个具有两个bool属性的类,并且订阅了一个observable,它提供包含各种值的对象(以不确定的速度)。例如:

bool IsActive {get; private set;}
bool IsBroken {get; private set;}
bool Status {get; private set;}
...
valueStream.GetValues().Subscribe(UpdateValues);

UpdateValues基于传入的对象做了一些工作。虽然有一些特别值得我使用的值用于某些特定的逻辑。我们称之为obj.SpecialValue:

private void UpdateValues(MyObject obj)
{
  ...
  Status = obj.SpecialValue;
  ...
}

现在,如果IsActive设置为true且Status为false,我想在将IsBroken设置为true之前等待三秒钟,以使流有机会在该时间内返回true的obj.SpecialValue。如果在三秒内它确实返回true,我就什么也不做,否则将IsBroken设置为true。更新Status或IsActive时,请再次检查。

起初我有:

Observable.Timer(TimeSpan.Zero, TimeSpan.FromSeconds(3)).Subscribe(SetIsBroken);

private void SetIsBroken()
{
  IsBroken = IsActive && !Status;
}

但是这比它需要的检查更多。它只需要检查流更新或IsActive何时更改。

有关如何以正确方式执行此操作的任何提示吗?

1 个答案:

答案 0 :(得分:1)

使用BehavourSubject<T>支持属性

此问题的一个有用的想法是使用BehaviorSubject<bool>类型支持您的属性。这些有用的服务于作为属性的属性和值的活动的双重目的。

您可以将它们订阅为可观察对象,但也可以通过Value属性访问它们的当前值。您可以通过OnNext发送新值来更改它们。

例如,我们可以这样做:

private BehaviorSubject<bool> _isActive;

public bool IsActive
{
    get { return _isActive.Value; }
    set { _isActive.OnNext(value); }
}

在所有属性中实现此功能后,查看您声明的复杂条件的属性变为相当简单的练习。假设_status_isBroken类似地实施了支持主题,我们可以设置这样的订阅:

Observable.CombineLatest(_isActive,
                         _status,
                        (a,s) => a & !s).DistinctUntilChanged()
          .Where(p => p)
          .SelectMany(_ => Observable.Timer(TimeSpan.FromSeconds(3), scheduler)
                                     .TakeUntil(_status.Where(st => st)))
          .Subscribe(_ => _isBroken.OnNext(true));  

部分行使用CombineLatest并订阅_isActive_status个流。只要其中任何一个发生变化,它就会发出 - 并且当_isActive为真且_status为假时,结果函数会精确设置真值。 DistinctUntilChanged()阻止将_isActive_status设置为启动新计时器时已有的值。

然后我们使用Where来过滤此条件。

SelectMany现在将采用真值并将每个值投影到3秒后发出的流中,使用Timer - 但是我们使用{{1}在TakeUntil变为true的情况下压缩此值。 _status还将流的流平坦化为单个布尔流。

此处不确定 - 您没有提及它,但您可能想要考虑SelectMany是否也应该终止计时器。如果是这种情况,您可以使用_isActiveMerge中合并手表和_status。

我们现在可以订阅这一整个事情来设置TakeUntil如果此查询有效,则表示计时器已过期。

注意_isBroken的{​​{1}}参数 - 这是存在的,因此我们可以传入测试调度程序。

我不确定我是否正确捕获了您的所有逻辑 - 但如果不希望您能在必要时看到如何修改。

这是完整的例子。使用nuget包scheduler,这将在LINQPad中运行,如下所示:

Timer

对评论的回应

如果你想让isActive为false将isBroken设为false,那么我这就是现在说的以下内容:

rx-testing

在这种情况下,请使用以下查询:

void Main()
{
    var tests = new Tests();
    tests.Test();
}

public class Foo
{
    private BehaviorSubject<bool> _isActive;
    private BehaviorSubject<bool> _isBroken;
    private BehaviorSubject<bool> _status;

    public bool IsActive
    {
        get { return _isActive.Value; }
        set { _isActive.OnNext(value); }
    }

    public bool IsBroken { get { return _isBroken.Value; } }
    public bool Status { get { return _status.Value; } }

    public Foo(IObservable<MyObject> valueStream, IScheduler scheduler)
    {
        _isActive = new BehaviorSubject<bool>(false);
        _isBroken = new BehaviorSubject<bool>(false);
        _status = new BehaviorSubject<bool>(false);

        // for debugging purposes
        _isActive.Subscribe(a => Console.WriteLine(
             "Time: " + scheduler.Now.TimeOfDay + " IsActive: " + a));
        _isBroken.Subscribe(a => Console.WriteLine(
             "Time: " + scheduler.Now.TimeOfDay + " IsBroken: " + a));
        _status.Subscribe(a => Console.WriteLine(
              "Time: " + scheduler.Now.TimeOfDay + " Status: " + a));

        valueStream.Subscribe(UpdateValues);

        Observable.CombineLatest(
                    _isActive,
                    _status,
                    (a,s) => a & !s).DistinctUntilChanged()
                .Where(p => p)
                .SelectMany(_ => Observable.Timer(TimeSpan.FromSeconds(3),
                                                  scheduler)
                                           .TakeUntil(_status.Where(st => st)))
                .Subscribe(_ => _isBroken.OnNext(true));            
    }

    private void UpdateValues(MyObject obj)
    {
        _status.OnNext(obj.SpecialValue);
    }   
}   

public class MyObject
{
    public MyObject(bool specialValue)
    {
        SpecialValue = specialValue;
    }

    public bool SpecialValue { get; set; }
}

public class Tests : ReactiveTest
{
    public void Test()
    {
        var testScheduler = new TestScheduler();

        var statusStream = testScheduler.CreateColdObservable<bool>(
            OnNext(TimeSpan.FromSeconds(1).Ticks, false),
            OnNext(TimeSpan.FromSeconds(3).Ticks, true),
            OnNext(TimeSpan.FromSeconds(5).Ticks, false));

        var activeStream = testScheduler.CreateColdObservable<bool>(
            OnNext(TimeSpan.FromSeconds(1).Ticks, false),
            OnNext(TimeSpan.FromSeconds(6).Ticks, true));           

        var foo = new Foo(statusStream.Select(b => new MyObject(b)), testScheduler);

        activeStream.Subscribe(b => foo.IsActive = b);       

        testScheduler.Start();               
    }        
}

请注意更改:

  • isActive isStatus Action T F Set Broken True after 3 seconds unless any other result occurs T T Set Broken False immediately if not already false, cancel timer F F Set Broken False immediately if not already false, cancel timer F T Set Broken False immediately if not already false, cancel timer 现在是Observable.CombineLatest( _isActive, _status, (a,s) => a & !s).DistinctUntilChanged() .Select(p => p ? Observable.Timer(TimeSpan.FromSeconds(3), scheduler) .Select(_ => true) : Observable.Return(false)) .Switch() .DistinctUntilChanged() .Subscribe(res => _isBroken.OnNext(res)); ,可将每个事件转变为其中之一
    • 3秒后返回SelectMany的计时器
    • 或立即Select
  • true的结果是bool流:false。我们希望任何新流出现以取消任何先前的流。这就是Select将要做的事情 - 在此过程中展平结果。
  • 我们现在应用第二个IObservable<IObservable<bool>>,因为取消的计时器可能会导致两个错误值连续出现在流中
  • 最后,我们将新兴的bool值分配给Switch