我正在使用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 到 true )。我还有一个可观察的 endObservable ,从同一个源observable查询,当按钮再次释放时会产生一个值(状态从 true 变为 false )。当 startObservable 产生一个值时,我会在每500毫秒开始一个可观察的间隔。我给这些值添加时间戳,然后从中取出,直到 endObservable 生成一个值。然后我返回一个持有 startObservable 值的对象,到目前为止还有多长时间。
我有一个单元测试,我按住按钮2秒钟(使用TestScheduler),释放它然后让调度程序再继续几秒钟。我这样做是为了确保在释放按钮后不再生成任何值。
这是我的测试失败的地方,也是我的问题的主题。在我的测试中,我期望产生4个值(在0.5s,1s,1.5s和2s之后)。但是,即使我使用 TakeUntil(endObservable)( digitalInputReleasedEventAdapter 生成 endObservable ,按钮发布后仍然会生成事件。 >有自己的一套测试,我确信它的工作方式应该如此。
我不认为我错误地构建了我的查询。我怀疑它可能与热与冷可观测量有关。但是,由于我刚开始使用RX,我并不完全了解这可能与我在这里遇到的问题有什么关系。
答案 0 :(得分: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;
}
答案 1 :(得分:0)
听起来你想要的只是在按钮关闭时才接收时间事件。 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);
}
}
}
答案 2 :(得分:-1)
不确定这是否是错误,但您的TimeStamp调用未包含测试调度程序。检查所有采用IScheduler参数的运算符,并确保它们传入测试调度程序。
答案 3 :(得分:-1)
我找到了解决方案。
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&lt;&gt;构造源可观察(此处称为 messageStream )。这产生了一些真实和虚假的价值观。我看到这个IEnumerable&lt;&gt;生成了好几次,所以必须启动几个线程。我相信产生的值是由Observable.Interval的单独实例消耗的,并且它们相互竞争以接收指示按钮释放的消息。所以现在我在startObservable和endObservable上调用了Publish()和Connect(),因此Observable.Interval实例共享相同的订阅。