.net 转发带有冷却的接收项目,当它们来的太快时切换到采样
我正在寻找Rx方法,该方法将采取可观察的方式,并将最新的物品置于“冷却”状态,这样当物品进入速度慢于冷却时间时,它们只会被转发,但当它们进入速度快时,您只会在每个冷却期后获得最新的值.net 转发带有冷却的接收项目,当它们来的太快时切换到采样,.net,system.reactive,throttling,.net,System.reactive,Throttling,我正在寻找Rx方法,该方法将采取可观察的方式,并将最新的物品置于“冷却”状态,这样当物品进入速度慢于冷却时间时,它们只会被转发,但当它们进入速度快时,您只会在每个冷却期后获得最新的值 public static IObservable<T> LimitRate<T>( this IObservable<T> source, TimeSpan duration, IScheduler scheduler) { re
public static IObservable<T> LimitRate<T>(
this IObservable<T> source, TimeSpan duration, IScheduler scheduler)
{
return source.DistinctUntilChanged()
.GroupByUntil(k => 0,
g => Observable.Timer(duration, scheduler))
.SelectMany(x => x.FirstAsync()
.Merge(x.Skip(1)
.TakeLast(1)))
.Select(x => Observable.Return(x)
.Concat(Observable.Empty<T>()
.Delay(duration, scheduler)))
.Concat();
}
换一种方式说,当项目之间的间隔小于t
时间时,我想切换到周期t
采样(当项目分散时切换回)
这与实际操作非常相似,只是每当新项目到达时,计时器不会重置
我想到的应用程序是通过网络发送“最新价值”更新。我不想传达一个值,除非它已经改变,我也不想对一个快速变化的值发送垃圾邮件,以至于我淹没了其他数据
是否有一种标准方法可以满足我的需要?您可以使用
Observable.DistinctUntilChanged
和Observable.Sample
可观察到。明显改变
仅当曲面值与以前的值不同时,此方法才会显示曲面值。()
可观察到。样本
Sample方法只是在每个指定的时间跨度内获取最后一个值。()
为了产生所需的效果,您可以将生成的第一个项目与上面描述的项目组合起来。Strilanc,考虑到您担心源流安静时会出现不必要的活动,您可能会对这种调整事件节奏的方法感兴趣-否则我不打算添加它,我认为J.Lennon的实现是完全合理的(而且简单得多),而且计时器的性能不会受到影响 在这个实现中还有一个有趣的区别——它不同于
Sample
方法,因为它会立即发出在冷却期之外发生的事件,而不是在下一个采样间隔。它在冷却时间之外不保持计时器
编辑-这里是v3解决了Chris在评论中提到的问题-它确保了在冷却过程中发生的变化会触发新的冷却期
public static IObservable<T> LimitRate<T>(
this IObservable<T> source, TimeSpan duration, IScheduler scheduler)
{
return source.DistinctUntilChanged()
.GroupByUntil(k => 0,
g => Observable.Timer(duration, scheduler))
.SelectMany(x => x.FirstAsync()
.Merge(x.Skip(1)
.TakeLast(1)))
.Select(x => Observable.Return(x)
.Concat(Observable.Empty<T>()
.Delay(duration, scheduler)))
.Concat();
}
自我回答
虽然我问的是Rx,但我的实际情况是它的一个端口(ReactiveCocoa)。更多的人知道Rx,我可以翻译
无论如何,我最终直接实现了它,以便它能够满足我想要的延迟/性能属性:
-(RACSignal*)cooldown:(NSTimeInterval)cooldownPeriod onScheduler:(RACScheduler *)scheduler {
need(cooldownPeriod >= 0);
need(!isnan(cooldownPeriod));
need(scheduler != nil);
need(scheduler != RACScheduler.immediateScheduler);
force(cooldownPeriod != 0); //todo: bother with no-cooldown case?
force(!isinf(cooldownPeriod)); //todo: bother with infinite case?
return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
need(subscriber != nil);
NSObject* lock = [NSObject new];
__block bool isCoolingDown = false;
__block bool hasDelayedValue = false;
__block id delayedValue = nil;
__block RACDisposable *cooldownDisposer = nil;
void (^onCanSendValue)(void) = ^{
@synchronized (lock) {
// check that we were actually cooling down
// (e.g. what if the system thrashed before we could dispose the running-down timer, causing a redundant call?)
if (!isCoolingDown) {
return;
}
// if no values arrived during the cooldown, we do nothing and can stop the timer for now
if (!hasDelayedValue) {
isCoolingDown = false;
[cooldownDisposer dispose];
return;
}
// forward latest value
id valueToSend = delayedValue;
hasDelayedValue = false;
delayedValue = nil;
// todo: can this be avoided?
// holding a lock while triggering arbitrary actions cam introduce subtle deadlock cases...
[subscriber sendNext:valueToSend];
}
};
void (^preemptivelyEndCooldown)(void) = ^{
// forward latest value AND ALSO force cooldown to run out (disposing timer)
onCanSendValue();
onCanSendValue();
};
RACDisposable *selfDisposable = [self subscribeNext:^(id x) {
bool didStartCooldown;
@synchronized (lock) {
hasDelayedValue = true;
delayedValue = x;
didStartCooldown = !isCoolingDown;
isCoolingDown = true;
}
if (didStartCooldown) {
// first item gets sent right away
onCanSendValue();
// coming items have to wait for the timer to run down
cooldownDisposer = [[RACSignal interval:cooldownPeriod onScheduler:scheduler]
subscribeNext:^(id _) { onCanSendValue(); }];
}
} error:^(NSError *error) {
preemptivelyEndCooldown();
[subscriber sendError:error];
} completed:^{
preemptivelyEndCooldown();
[subscriber sendCompleted];
}];
return [RACDisposable disposableWithBlock:^{
[selfDisposable dispose];
@synchronized (lock) {
isCoolingDown = false;
[cooldownDisposer dispose];
}
}];
}] setNameWithFormat:@"[%@ cooldown:%@]", self.name, @(cooldownPeriod)];
}
-(RACSignal*)冷却:(NSTIMEIVAL)调度程序上的冷却周期:(RACScheduler*)调度程序{
需要(冷却期>=0);
需要(!isnan(冷却期));
需要(调度程序!=nil);
需要(调度器!=RACScheduler.immediateScheduler);
force(冷却周期!=0);//todo:不考虑冷却案例?
force(!isinf(冷却期));//todo:麻烦处理无限大的情况吗?
return[[RACSignal createSignal:^(id订户){
需要(订户!=无);
NSObject*lock=[NSObject new];
__块bool isCoolingDown=false;
__块bool hasdayedvalue=false;
__块id delayedValue=nil;
__块RAC*冷却处理器=无;
作废(^onCanSendValue)(作废)=^{
@已同步(锁定){
//检查我们是否真的在冷却
//(例如,如果在我们可以处理正在运行的计时器之前,系统崩溃,导致冗余呼叫,该怎么办?)
如果(!isCoolingDown){
返回;
}
//如果在冷却期间没有值到达,我们什么也不做,现在可以停止计时器
如果(!hasDelayedValue){
isCoolingDown=false;
[冷却处置器处置];
返回;
}
//远期最新价值
id valueToSend=延迟值;
hasDelayedValue=false;
延迟值=零;
//托多:这可以避免吗?
//在触发任意操作时持有锁会导致微妙的死锁情况。。。
[订户发送下一步:valueToSend];
}
};
无效(^优先权和冷却)(无效)=^{
//转发最新值,并强制冷却耗尽(处理计时器)
onCanSendValue();
onCanSendValue();
};
RACDisposable*selfDisposable=[self subscribeNext:^(id x){
布尔·迪德斯塔特·科尔敦;
@已同步(锁定){
hasDelayedValue=true;
延迟值=x;
DIDSTARTCOLDOWN=!正在冷却;
isCoolingDown=true;
}
如果(DIDSTARTCOLDOWN){
//第一个项目立即发送
onCanSendValue();
//即将到来的项目必须等待计时器运行
cooldownDisposer=[[RACSignal interval:cooldownPeriod onScheduler:scheduler]
subscribeNext:^(id){onCanSendValue();}];
}
}错误:^(N错误*错误){
抢占式冷却();
[订户发送错误:错误];
}已完成:^{
抢占式冷却();
[用户发送完成];
}];
返回[RAC一次性处置带块:^{
[自行处置];
@已同步(锁定){
isCoolingDown=false;
[冷却处置器处置];
}
}];
}]setNameWithFormat:@“[%@冷却时间:%@]”,self.name,@(冷却时间)];
}
它应该几乎直接转换为.Net RX。当物品停止到达时,它将停止执行任何工作,并将在尊重冷却时间的情况下尽快转发物品。我意识到这一问题已经解决了一段时间,但我想提供另一种解决方案,我可以
public static IObservable<T> SampleImmediate<T>(this IObservable<T> source, TimeSpan dueTime)
{
return source
.GroupBy(x => 0)
.SelectMany(group =>
{
return Observable.Create<T>(o =>
{
var connectable = group.Materialize().Publish();
var sub = Observable.Merge(
connectable.Sample(dueTime),
connectable.Take(1)
)
.DistinctUntilChanged()
.Dematerialize()
.Subscribe(o);
return new CompositeDisposable(connectable.Connect(), sub);
});
});
}
public static IObservable<T> Cooldown<T>(this IObservable<T> source, TimeSpan dueTime)
{
return source
.GroupByUntil(x => 0, group => group.Throttle(dueTime))
.SelectMany(group => group.SampleImmediate(dueTime));
}