C# 反应条件节流算子

C# 反应条件节流算子,c#,.net,reactive-programming,system.reactive,C#,.net,Reactive Programming,System.reactive,我正在寻找一个类似的运算符,但节流行为仅在布尔值为true时应用。因此,给定两个可观察的IObservable值和IObservable throttleCondition,我想创建一个可观察的,它执行以下操作: 将通过值中的所有值,直到 当throttleCondition生成true时。然后,直到 当throttleCondition再次生成false时。然后传播序列的最后一个值,并再次传递任何新值 或者,用大理石图表示(注意4): 我得到的最接近的结果是: public static

我正在寻找一个类似的运算符,但节流行为仅在布尔值为true时应用。因此,给定两个可观察的
IObservable值
IObservable throttleCondition
,我想创建一个可观察的,它执行以下操作:

  • 将通过
    值中的所有值,直到
  • throttleCondition
    生成
    true
    时。然后,直到
  • throttleCondition
    再次生成
    false
    时。然后传播序列的最后一个值,并再次传递任何新值
或者,用大理石图表示(注意4):

我得到的最接近的结果是:

public static IObservable<T> ThrottleWhen<T>(this IObservable<T> self, IObservable<bool> throttleCondition)
{
    bool cond = false;
    throttleCondition.Subscribe(v => cond = v);
    return self.Window(throttleCondition).Select(obs => cond ? obs.TakeLast(1) : obs).SelectMany(obs => obs);
}
public static IObservable ThrottleWhen(此IObservable self,IObservable throttleCondition)
{
bool cond=假;
throttleCondition.Subscribe(v=>cond=v);
返回self.Window(throttleCondition).Select(obs=>cond?obs.TakeLast(1):obs.SelectMany(obs=>obs);
}
但这不是线程安全的,因为Select和Subscribe之间可能存在竞争。 有人有什么建议吗?也许已经有一个接线员了,但我没有看到

编辑: 下面是我需要的函数的单元测试:

[TestMethod]
public void TestThrottleWhen()
{
    //Setup
    var scheduler = new TestScheduler();
    Subject<int> numberValues = new Subject<int>();
    Subject<bool> flagValues = new Subject<bool>();

    //Define actions
    scheduler.Schedule(TimeSpan.FromTicks(1), () => flagValues.OnNext(false));
    scheduler.Schedule(TimeSpan.FromTicks(10), () => numberValues.OnNext(0));
    scheduler.Schedule(TimeSpan.FromTicks(20), () => numberValues.OnNext(1));
    scheduler.Schedule(TimeSpan.FromTicks(30), () => flagValues.OnNext(true));
    scheduler.Schedule(TimeSpan.FromTicks(40), () => numberValues.OnNext(2));
    scheduler.Schedule(TimeSpan.FromTicks(50), () => numberValues.OnNext(3));
    scheduler.Schedule(TimeSpan.FromTicks(60), () => numberValues.OnNext(4));
    scheduler.Schedule(TimeSpan.FromTicks(70), () => flagValues.OnNext(false));
    scheduler.Schedule(TimeSpan.FromTicks(71), () => flagValues.OnNext(true));
    scheduler.Schedule(TimeSpan.FromTicks(72), () => flagValues.OnNext(false));
    scheduler.Schedule(TimeSpan.FromTicks(80), () => numberValues.OnNext(5));
    scheduler.Schedule(TimeSpan.FromTicks(90), () => numberValues.OnNext(6));
    var actual = scheduler.Start(() => numberValues.ThrottleWhen(flagValues), 0, 0, 100);

    //Assert
    var expected = new[]
    {
        ReactiveTest.OnNext(10, 0),
        ReactiveTest.OnNext(20, 1),
        ReactiveTest.OnNext(70, 4),
        ReactiveTest.OnNext(80, 5),
        ReactiveTest.OnNext(90, 6)

    };
    ReactiveAssert.AreElementsEqual(expected, actual.Messages);
}
[TestMethod]
公共void TestThrottleWhen()
{
//设置
var scheduler=newtestscheduler();
主题编号值=新主题();
Subject flagValues=新主题();
//定义动作
scheduler.Schedule(TimeSpan.FromTicks(1),()=>flagValues.OnNext(false));
scheduler.Schedule(TimeSpan.FromTicks(10),()=>numberValues.OnNext(0));
scheduler.Schedule(TimeSpan.FromTicks(20),()=>numberValues.OnNext(1));
scheduler.Schedule(TimeSpan.FromTicks(30),()=>flagValues.OnNext(true));
scheduler.Schedule(TimeSpan.FromTicks(40),()=>numberValues.OnNext(2));
scheduler.Schedule(TimeSpan.FromTicks(50),()=>numberValues.OnNext(3));
scheduler.Schedule(TimeSpan.FromTicks(60),()=>numberValues.OnNext(4));
scheduler.Schedule(TimeSpan.FromTicks(70),()=>flagValues.OnNext(false));
scheduler.Schedule(TimeSpan.FromTicks(71),()=>flagValues.OnNext(true));
scheduler.Schedule(TimeSpan.FromTicks(72),()=>flagValues.OnNext(false));
scheduler.Schedule(TimeSpan.FromTicks(80),()=>numberValues.OnNext(5));
scheduler.Schedule(TimeSpan.FromTicks(90),()=>numberValues.OnNext(6));
var-actual=scheduler.Start(()=>numberValues.ThrottleWhen(flagValues),0,0,100);
//断言
预期风险值=新[]
{
反应性测试.OnNext(10,0),
反应性测试.OnNext(20,1),
反应性测试.OnNext(70,4),
反应性测试.OnNext(80,5),
反应性测试.OnNext(90,6)
};
ReactiveAssert.arElementSequal(预期的、实际的.Messages);
}
编辑2: 我最终使用了Alex答案的修改版本:

public static IObservable<T> ThrottleWhen<T>(this IObservable<T> self, IObservable<bool> throttleCondition)
{
    var isPaused = throttleCondition.Prepend(false).DistinctUntilChanged();
    return Observable.Defer(() =>
    {
        object lockObj = new object();
        bool gateIsOpen = false;
        return Observable.CombineLatest(
                self.Synchronize(lockObj).Do(_ => gateIsOpen = true),
                isPaused.Synchronize(lockObj).Do(paused => gateIsOpen = !paused && gateIsOpen),
                (number, paused) => (number, paused)
            )
            .Where(tuple => !tuple.paused && gateIsOpen)
            .Select(tuple => tuple.number);
    });
}
public static IObservable ThrottleWhen(此IObservable self,IObservable throttleCondition)
{
var isPaused=throttleCondition.Prepend(false).DistinctUntilChanged();
返回可观察的延迟(()=>
{
object lockObj=新对象();
bool-gateIsOpen=false;
返回可观测的(
self.Synchronize(lockObj).Do(=>gateIsOpen=true),
isPaused.Synchronize(lockObj.Do)(暂停=>gateIsOpen=!暂停(&gateIsOpen)),
(数字,暂停)=>(数字,暂停)
)
.Where(tuple=>!tuple.paused&&gateIsOpen)
.Select(tuple=>tuple.number);
});
}

我希望这将是最后一次尝试^^

回到CombineTest方法,结合您的解决方案,我提出了一个额外的标志,它将帮助我们避免出现这样的情况,即标志来回变化,而可观察的值不会发出任何值

bool numberChangedFlag = false;

_numberValues
    .Do(_ => numberChangedFlag = true)
    .CombineLatest(_flagValues.Do(x => numberChangedFlag = !x && numberChangedFlag), (number, flag) => (number, flag))
    .Where(tuple => !tuple.flag && numberChangedFlag)
    .Select(tuple => tuple.number)
    .Subscribe(DoYourMagic);
我在你的测试中试过了,看起来效果不错

虽然我不喜欢我们需要一个局部helper变量来解决这个问题,但深入一点并创建一个helper observable似乎会使代码过于复杂


让我知道这次是否有效。:)

我希望这将是最后一次尝试^^

回到CombineTest方法,结合您的解决方案,我提出了一个额外的标志,它将帮助我们避免出现这样的情况,即标志来回变化,而可观察的值不会发出任何值

bool numberChangedFlag = false;

_numberValues
    .Do(_ => numberChangedFlag = true)
    .CombineLatest(_flagValues.Do(x => numberChangedFlag = !x && numberChangedFlag), (number, flag) => (number, flag))
    .Where(tuple => !tuple.flag && numberChangedFlag)
    .Select(tuple => tuple.number)
    .Subscribe(DoYourMagic);
我在你的测试中试过了,看起来效果不错

虽然我不喜欢我们需要一个局部helper变量来解决这个问题,但深入一点并创建一个helper observable似乎会使代码过于复杂


让我知道这次是否有效。:)

我认为这更干净,但干净在旁观者眼里:

在核心部分,解决方案如下所示:

public static IObservable<T> ThrottleWhenCore<T>(this IObservable<T> self, IObservable<bool> throttleCondition)
{

    return throttleCondition
        .StartWith(false)
        .Join(self,
            b => b ? Observable.Empty<bool>() : throttleCondition,
            t => self,
            (b, t) => (b, t)
        )
        .Where(tuple => !tuple.b)
        .Select(tuple => tuple.t);
}

如果你需要更多的解释,让我知道。与锁定/状态变量相比,我更喜欢它。

我认为这更干净,但“干净”是旁观者眼中的:

在核心部分,解决方案如下所示:

public static IObservable<T> ThrottleWhenCore<T>(this IObservable<T> self, IObservable<bool> throttleCondition)
{

    return throttleCondition
        .StartWith(false)
        .Join(self,
            b => b ? Observable.Empty<bool>() : throttleCondition,
            t => self,
            (b, t) => (b, t)
        )
        .Where(tuple => !tuple.b)
        .Select(tuple => tuple.t);
}

如果你需要更多的解释,让我知道。与锁定/状态变量相比,我更喜欢这种方式。

这里还有另一种可能性,基于@Enigmativity的评论。考虑这一点的最佳方式是将其分为两个问题:当条件变为真时获取最新信息,当条件变为假时自由发射,然后将两者合并在一起

public static IObservable<T> ThrottleWhen<T>(this IObservable<T> self, IObservable<bool> throttleCondition)
{

    return throttleCondition.Publish(_throttleCondition => self.Publish(_self => Observable.Merge(
        _throttleCondition   //Get latest when true handler
            .Where(b => !b)
            .WithLatestFrom(
                _self.Select((item, index) => (item, index)),
                (_, t) => t
            )
            .DistinctUntilChanged()
            .Select(t => t.item),

        _throttleCondition   //Freely emit when false, default start with false.
            .StartWith(false)
            .Select(b => b ? Observable.Empty<T>(): _self)
            .Switch()

    )));
}
public static IObservable ThrottleWhen(此IObservable self,IObservable throttleCondition)
{
返回throttleCondition.Publish(\u throttleCondition=>self.Publish(\u self=>observeable.Merge(
_throttleCondition//Get latest when true处理程序
.其中(b=>!b)
.最晚从(
_self.Select((项目,索引)=>(项目,索引)),
(u,t)=>t
)