【问题标题】:Take from Observable.Interval until another observable produces a value从 Observable.Interval 中获取,直到另一个 observable 产生一个值
【发布时间】:2013-01-11 12:35:04
【问题描述】:

我正在使用 RX 来查询来自自动化设备的事件,该设备连接有按钮。我希望能够区分用户刚刚按下并立即释放按钮,或者他是否按住按钮一段时间。我用来查看他是否按住按钮的查询有问题。

基本思想是一旦按钮被按下,我会每半秒产生一个值,直到按钮再次被释放。我还为每个值加上时间戳,这样我就知道按钮被按下了多长时间。

这是我的代码,以及我认为它应该如何工作:

public IObservable<DigitalInputHeldInfo> Adapt(IObservable<EventPattern<AdsNotificationEventArgs>> messageStream) {
  var startObservable = 
    _digitalInputPressedEventAdapter.Adapt(messageStream);
  var endObservable = 
    _digitalInputReleasedEventAdapter.Adapt(messageStream);

  return from notification in startObservable.Timestamp()

    from interval in 
        Observable.
        Interval(
            500.Milliseconds(),
            _schedulerProvider.ThreadPool).
        Timestamp().
        TakeUntil(endObservable)

    select new DigitalInputHeldInfo(
            interval.Timestamp.Subtract(notification.Timestamp),
            notification.Value);
}

从给定的 IObservable,我正在对其应用查询,以便我有一个可观察的 startObservable,每次按下按钮时都会产生一个值(状态从 false)。我还有一个 observable endObservable,从同一个源 observable 查询,它在按钮再次释放时产生一个值(状态从 true 变为 false em>)。当 startObservable 产生一个值时,我每 500 毫秒开始一个可观察的时间间隔。我给这些值加上时间戳,并从中获取,直到 endObservable 产生一个值。然后我返回一个对象,该对象保存 startObservable 的值,以及到目前为止已经保存了多长时间。

我对此有一个单元测试,我按住按钮 2 秒钟(使用 TestScheduler),释放它,然后让调度程序继续运行几秒钟。我这样做是为了确保在释放按钮后不再产生任何值。

这是我的测试失败的地方,也是我的问题的主题。在我的测试中,我预计会产生 4 个值(在 0.5 秒、1 秒、1.5 秒和 2 秒之后)。但是,即使我正在使用 TakeUntil(endObservable)(产生 endObservabledigitalInputReleasedEventAdapter,在释放按钮后仍然会产生事件> 有自己的一组测试,我相信它会按应有的方式工作)。

我认为我的查询构造不正确。我怀疑这可能与热与冷的可观察物有关。但由于我刚开始使用 RX,我并不完全了解这可能与我在这里遇到的问题有什么关系。

【问题讨论】:

  • 看起来您正在以相当复杂的方式进行操作。为什么不只测量两个事件之间的时间
  • 我这样做是因为我不知道按钮会被按住多长时间。因此,释放按钮时的事件可能会在 1 或 1000 秒后发生。与此同时,我仍然希望得到中间事件,告诉我它被按下了多长时间,所以应用程序可以做出不同的反应。当按钮被按下 2 秒时我们可能会做一些事情,然后如果它仍然被按住 5 秒后做其他事情。无论如何,我仍在调查它,看起来我的查询正在做它应该做的事情。我会在接下来的几天里在这里发布更新。
  • 如果您有一个事件开始时带有时间戳,那么您可以从当前时钟计算持续时间,而没有额外的事件。

标签: c# system.reactive


【解决方案1】:

不确定这是否完全符合您的要求,但它是“另一种方法”:

void Main()
{
    // ButtonPush returns IObservable<bool>
    var buttonPusher = ButtonPush();
    var pushTracker = 
        from pushOn in buttonPusher.Where(p => p).Timestamp()
        let tickWindow = Observable.Interval(TimeSpan.FromMilliseconds(500))
        from tick in tickWindow
            .TakeUntil(buttonPusher.Where(p => !p))
            .Timestamp()
            .Select(t => t.Timestamp)
        select tick;

    Console.WriteLine("Start: {0}", Observable.Return(true).Timestamp().First().Timestamp);
    var s = pushTracker
        .SubscribeOn(NewThreadScheduler.Default)
        .Subscribe(x => Console.WriteLine("tick:{0}", x));
}

IObservable<bool> ButtonPush()
{
    var push = new BehaviorSubject<bool>(false);    
    var rnd = new Random();
    Task.Factory.StartNew(
        () => 
        {
            // "press button" after random # of seconds
            Thread.Sleep(rnd.Next(2, 5) * 1000);
            push.OnNext(true);
            // and stop after another random # of seconds
            Thread.Sleep(rnd.Next(3, 10) * 1000);
            push.OnNext(false);
        });
    return push;
}

【讨论】:

    【解决方案2】:

    听起来你想要的只是在按钮按下时接收时间事件。 CombineLatest 应该可以帮到你。

    例如:

    using Microsoft.Reactive.Testing;
    using System;
    using System.Diagnostics;
    using System.Reactive.Linq;
    using System.Reactive.Subjects;
    
    namespace ButtonTest
    {
        class Program
        {
            enum State
            {
                KeyDown, KeyUp
            }
    
            static void Main(string[] args)
            {
                var buttonState = new BehaviorSubject<State>(State.KeyUp);
                var testScheduler = new TestScheduler();
                var events = testScheduler.CreateObserver<long>();
    
                Observable.Interval(TimeSpan.FromTicks(100), testScheduler)
                    .CombineLatest(buttonState, (t,s)=> new { TimeStamp = t, ButtonState = s })
                    .Where(t => t.ButtonState == State.KeyDown)
                    .Select(t => t.TimeStamp)
                    .Subscribe(events);
    
                testScheduler.AdvanceBy(100);//t=0
                testScheduler.AdvanceBy(100);//t=1
    
                buttonState.OnNext(State.KeyDown);
                testScheduler.AdvanceBy(100);//t=2
    
                testScheduler.AdvanceBy(100);//t=3
                buttonState.OnNext(State.KeyUp);
    
                testScheduler.AdvanceBy(100);//t=4
                testScheduler.AdvanceBy(100);//t=5
    
                buttonState.OnNext(State.KeyDown);
                testScheduler.AdvanceBy(100);//t=6
    
                buttonState.OnNext(State.KeyUp);
    
                testScheduler.AdvanceBy(100);//t=7
                testScheduler.AdvanceBy(100);//t=8
    
                Debug.Assert(events.Messages.Count == 5);
                Debug.Assert(events.Messages[0].Value.Value == 1);
                Debug.Assert(events.Messages[1].Value.Value == 2);
                Debug.Assert(events.Messages[2].Value.Value == 3);
                Debug.Assert(events.Messages[3].Value.Value == 5);
                Debug.Assert(events.Messages[4].Value.Value == 6);
            }
        }
    }
    

    【讨论】:

      【解决方案3】:

      不确定这是否是错误,但您的 TimeStamp 调用未包含测试调度程序。检查所有采用 IScheduler 参数的运算符,并确保它们通过了测试调度程序。

      【讨论】:

        【解决方案4】:

        我找到了解决办法。

        public IObservable<DigitalInputHeldInfo> Adapt(
          IObservable<EventPattern<AdsNotificationEventArgs>> messageStream) {
        
          var startObservable = _digitalInputPressedEventAdapter.
              Adapt(messageStream).
              Publish();
          var endObservable = _digitalInputReleasedEventAdapter.
              Adapt(messageStream).
              Publish();
        
          startObservable.Connect();
          endObservable.Connect();
        
          return from notification in startObservable.Timestamp()
                 from interval in Observable.Interval(500.Milliseconds(), 
                                                      _schedulerProvider.ThreadPool).
                                  Timestamp().
                                  TakeUntil(endObservable)
                 select new DigitalInputHeldInfo(
                        interval.Timestamp.Subtract(notification.Timestamp), 
                        notification.Value);
        }
        

        我将这段代码隔离到一个控制台应用程序中,并从一个 IEnumerable 构造了源 observable(这里称为 messageStream),它产生了一些真值和假值。我看到这个 IEnumerable 生成了几次,所以肯定有几个线程启动了。我相信生成的值被 Observable.Interval 的不同实例消耗,并且这些实例相互竞争以接收指示按钮释放的消息。所以现在我在 startObservable 和 endObservable 上调用 Publish() 和 Connect(),所以 Observable.Interval 实例共享同一个订阅。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-05-25
          • 1970-01-01
          • 1970-01-01
          • 2013-02-19
          相关资源
          最近更新 更多