C# 以偶数间隔推送缓冲事件的一种方法
我试图实现的是缓冲来自一些IObservable的传入事件(它们以突发的方式出现),并进一步释放它们,但一个接一个地释放,间隔甚至相等。 像这样:C# 以偶数间隔推送缓冲事件的一种方法,c#,.net,system.reactive,C#,.net,System.reactive,我试图实现的是缓冲来自一些IObservable的传入事件(它们以突发的方式出现),并进一步释放它们,但一个接一个地释放,间隔甚至相等。 像这样: -oo-ooo-oo------------------oooo-oo-o--------------> -o--o--o--o--o--o--o--------o--o--o--o--o--o--o----> 由于我对Rx非常陌生,所以我不确定是否已经有一个主题或一个操作员这样做了。也许可以通过构图来完成 更新: 多亏了 为了指出D
-oo-ooo-oo------------------oooo-oo-o-------------->
-o--o--o--o--o--o--o--------o--o--o--o--o--o--o---->
由于我对Rx非常陌生,所以我不确定是否已经有一个主题或一个操作员这样做了。也许可以通过构图来完成
更新:
多亏了
为了指出Drain操作符,我找到了另一个Drain操作符用法。以下是我如何让它在WPF应用程序中工作的:
.Drain(x => {
Process(x);
return Observable.Return(new Unit())
.Delay(TimeSpan.FromSeconds(1), Scheduler.Dispatcher );
}).Subscribe();
我玩得很开心,因为省略scheduler参数会导致应用程序在调试模式下崩溃,而不会出现任何异常(我需要学习如何在Rx中处理异常)。
Process方法直接修改UI状态,但我想用它来创建IObservable(使用ISubject?)相当简单
更新:
与此同时,我一直在使用ISubject进行实验,下面的类实现了我想要的功能—它可以及时释放缓冲的Ts:
public class StepSubject<T> : ISubject<T>
{
IObserver<T> subscriber;
Queue<T> queue = new Queue<T>();
MutableDisposable cancel = new MutableDisposable();
TimeSpan interval;
IScheduler scheduler;
bool idle = true;
public StepSubject(TimeSpan interval, IScheduler scheduler)
{
this.interval = interval;
this.scheduler = scheduler;
}
void Step()
{
T next;
lock (queue)
{
idle = queue.Count == 0;
if (!idle)
next = queue.Dequeue();
}
if (!idle)
{
cancel.Disposable = scheduler.Schedule(Step, interval);
subscriber.OnNext(next);
}
}
public void OnNext(T value)
{
lock (queue)
queue.Enqueue(value);
if (idle)
cancel.Disposable = scheduler.Schedule(Step);
}
public IDisposable Subscribe(IObserver<T> observer)
{
subscriber = observer;
return cancel;
}
}
公共类步骤主题:ISubject
{
IObserver用户;
队列=新队列();
MutableDisposable cancel=新的MutableDisposable();
时间间隔;
IScheduler调度器;
bool idle=true;
公共StepSubject(时间跨度间隔,ISScheduler调度程序)
{
这个。间隔=间隔;
this.scheduler=调度程序;
}
无效步骤()
{
T下一步;
锁(队列)
{
空闲=队列。计数==0;
如果(!空闲)
next=queue.Dequeue();
}
如果(!空闲)
{
cancel.Disposable=scheduler.Schedule(步骤,间隔);
subscriber.OnNext(下一个);
}
}
公共void OnNext(T值)
{
锁(队列)
queue.Enqueue(值);
如果(空闲)
cancel.Disposable=scheduler.Schedule(步骤);
}
公共IDisposable订阅(IObserver观察员)
{
订户=观察员;
返回取消;
}
}
为了清晰起见,这个幼稚的实现从OnCompleted和OnError中剥离出来,并且只允许一次订阅。它实际上比听起来更狡猾 使用
Delay
不起作用,因为这些值仍然会大量出现,只是稍微延迟
将Interval
与combinelateest
或Zip
一起使用不起作用,因为前者将导致跳过源值,后者将缓冲区间值
我认为新的Drain
操作符(),加上Delay
应该可以做到:
source.Drain(x => Observable.Empty<int>().Delay(TimeSpan.FromSeconds(1)).StartWith(x));
为了完整起见,这里是Richard建议的Drain()方法的一个替代(更紧凑)版本:
public static IObservable<T2> SelectManySequential<T1, T2>(
this IObservable<T1> source,
Func<T1, IObservable<T2>> selector
)
{
return source
.Select(x => Observable.Defer<T2>(() => selector(x)))
.Concat();
}
publicstaticiobservable SelectManySequential(
这是一个可观测的来源,
函数选择器
)
{
返回源
.Select(x=>Observable.Defer(()=>selector(x)))
.Concat();
}
查看Rx论坛中的线程
更新:
我意识到我使用的Concat()重载是我个人的Rx扩展之一,它(尚未)是框架的一部分。我为这个错误感到抱歉。。当然,这使得我的解决方案没有我想象的那么优雅
然而,为了完整性,我在这里发布了我的Conact()扩展方法重载:
public static IObservable<T> Concat<T>(this IObservable<IObservable<T>> source)
{
return Observable.CreateWithDisposable<T>(o =>
{
var lockCookie = new Object();
bool completed = false;
bool subscribed = false;
var waiting = new Queue<IObservable<T>>();
var pendingSubscription = new MutableDisposable();
Action<Exception> errorHandler = e =>
{
o.OnError(e);
pendingSubscription.Dispose();
};
Func<IObservable<T>, IDisposable> subscribe = null;
subscribe = (ob) =>
{
subscribed = true;
return ob.Subscribe(
o.OnNext,
errorHandler,
() =>
{
lock (lockCookie)
{
if (waiting.Count > 0)
pendingSubscription.Disposable = subscribe(waiting.Dequeue());
else if (completed)
o.OnCompleted();
else
subscribed = false;
}
}
);
};
return new CompositeDisposable(pendingSubscription,
source.Subscribe(
n =>
{
lock (lockCookie)
{
if (!subscribed)
pendingSubscription.Disposable = subscribe(n);
else
waiting.Enqueue(n);
}
},
errorHandler
, () =>
{
lock (lockCookie)
{
completed = true;
if (!subscribed)
o.OnCompleted();
}
}
)
);
});
}
公共静态IObservable Concat(此IObservable源)
{
返回可观察的。CreateWithDisposable(o=>
{
var lockCookie=新对象();
bool completed=false;
bool=false;
var waiting=新队列();
var pendingSubscription=new MutableDisposable();
动作errorHandler=e=>
{
o、 OnError(e);
pendingSubscription.Dispose();
};
Func subscribe=null;
订阅=(ob)=>
{
订阅=真;
返回ob.Subscribe(
o、 OnNext,
错误处理程序,
() =>
{
锁(lockCookie)
{
如果(waiting.Count>0)
pendingSubscription.Disposable=subscribe(waiting.Dequeue());
否则,如果(已完成)
o、 未完成();
其他的
订阅=假;
}
}
);
};
返回新的CompositeDisposable(pendingSubscription,
来源:订阅(
n=>
{
锁(lockCookie)
{
如果(!已订阅)
pendingSubscription.Disposable=订阅(n);
其他的
排队(n);
}
},
错误处理程序
, () =>
{
锁(lockCookie)
{
完成=正确;
如果(!已订阅)
o、 未完成();
}
}
)
);
});
}
现在用我自己的武器打败我自己:
同样的Concat()方法可以用Richard Szalay的绝妙方式编写得更加优雅:
public static IObservable<T> Concat<T>(this IObservable<IObservable<T>> source)
{
return Observable.Defer(() =>
{
BehaviorSubject<Unit> queue = new BehaviorSubject<Unit>(new Unit());
return source
.Zip(queue, (v, q) => v)
.SelectMany(v =>
v.Do(_ => { }, () => queue.OnNext(new Unit()))
);
});
}
公共静态IObservable Concat(此IObservable源)
{
返回可观察的延迟(()=>
{
BehaviorSubject队列=新的BehaviorSubject(新单元());
返回源
.Zip(队列,(v,q)=>v)
.SelectMany(v=>
v、 Do(=>{},()=>queue.OnNext(新单元())
);
});
}
因此,信用属于Richard.:-) 我就是这样做的,只是使用一个显式队列(ReactiveCollection只是WPF的ObservableCollection-ReactiveCollection.ItemsAdded OnNext for eac的一个奇特版本)
public static IObservable<T> Concat<T>(this IObservable<IObservable<T>> source)
{
return Observable.Defer(() =>
{
BehaviorSubject<Unit> queue = new BehaviorSubject<Unit>(new Unit());
return source
.Zip(queue, (v, q) => v)
.SelectMany(v =>
v.Do(_ => { }, () => queue.OnNext(new Unit()))
);
});
}
public static ReactiveCollection<T> CreateCollection<T>(this IObservable<T> FromObservable, TimeSpan? WithDelay = null)
{
var ret = new ReactiveCollection<T>();
if (WithDelay == null) {
FromObservable.ObserveOn(RxApp.DeferredScheduler).Subscribe(ret.Add);
return ret;
}
// On a timer, dequeue items from queue if they are available
var queue = new Queue<T>();
var disconnect = Observable.Timer(WithDelay.Value, WithDelay.Value)
.ObserveOn(RxApp.DeferredScheduler).Subscribe(_ => {
if (queue.Count > 0) {
ret.Add(queue.Dequeue());
}
});
// When new items come in from the observable, stuff them in the queue.
// Using the DeferredScheduler guarantees we'll always access the queue
// from the same thread.
FromObservable.ObserveOn(RxApp.DeferredScheduler).Subscribe(queue.Enqueue);
// This is a bit clever - keep a running count of the items actually
// added and compare them to the final count of items provided by the
// Observable. Combine the two values, and when they're equal,
// disconnect the timer
ret.ItemsAdded.Scan0(0, ((acc, _) => acc+1)).Zip(FromObservable.Aggregate(0, (acc,_) => acc+1),
(l,r) => (l == r)).Where(x => x != false).Subscribe(_ => disconnect.Dispose());
return ret;
}