Take from Observable.Interval until another observable produces a value
我正在使用RX查询自动化设备发出的事件,该设备已连接了按钮。我希望能够分辨出用户何时按下并立即释放按钮,或者他是否按住按钮一段时间。我使用的查询有问题,看他是否按住了该按钮。
基本思想是,一旦按下按钮,我将每半秒产生一个值,直到再次释放按钮为止。我还要给每个值加上时间戳,以便我知道按钮被按下了多长时间。
这是我的代码,以及我认为这应该如何工作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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变为true)。我还有一个可观察的endObservable,从可观察的同一源查询到的,当再次释放按钮时会产生一个值(状态从true变为false)。当startObservable产生一个值时,我每500毫秒启动一个可观察的间隔。我给这些值加上时间戳,然后从中获取直到endObservable产生一个值。然后,我返回一个对象,该对象保存startObservable的值,以及到现在为止已经保存了多长时间。
为此,我进行了单元测试,其中按住按钮2秒钟(使用TestScheduler),将其释放,然后让调度程序继续运行几秒钟。我这样做是为了确保释放按钮后不再产生任何值。
这是我的测试失败的地方,也是我的问题的主题。在我的测试中,我期望会生成4个值(在0.5s,1s,1.5s和2s之后)。但是,即使我使用的是TakeUntil(endObservable),释放按钮后仍然会产生事件(产生endObservable的digitalInputReleasedEventAdapter拥有自己的一组测试,并且我相信它会按应有的方式工作) 。
我认为我的查询构造不正确。我怀疑这可能与热观测值与冷观测值有关。但是,由于我刚开始使用RX,因此我对这可能与我在这里遇到的问题有何关系还没有完全了解。
不知道这是否确实符合您的要求,但这是"另一种方法":
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | 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; } |
听起来您想要的是仅在按钮按下时才接收时间事件。
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | 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); } } } |
我找到了解决方案。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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 <>构建了可观察的源(这里称为messageStream),该源产生了一些正确和错误的值。我看到此IEnumerable <>生成了几次,因此必须启动了多个线程。我相信产生的值已被Observable.Interval的单独实例占用,并且它们相互竞争以接收指示按钮释放的消息。所以现在我在startObservable和endObservable上调用Publish()和Connect(),因此Observable.Interval实例共享相同的订阅。
不知道这是否是错误,但是您的TimeStamp调用未包含测试计划程序。检查所有采用IScheduler参数的运算符,并确保它们正在通过测试调度程序。