我有一个具有两个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何时更改。
有关如何以正确方式执行此操作的任何提示吗?
答案 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
是否也应该终止计时器。如果是这种情况,您可以使用_isActive
在Merge
中合并手表和_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));
,可将每个事件转变为其中之一
SelectMany
的计时器Select
true
的结果是bool流:false
。我们希望任何新流出现以取消任何先前的流。这就是Select
将要做的事情 - 在此过程中展平结果。IObservable<IObservable<bool>>
,因为取消的计时器可能会导致两个错误值连续出现在流中Switch
。