我想有效地限制事件流,以便在收到第一个事件时调用我的委托,但如果收到后续事件则不会持续1秒。在超时(1秒)到期后,如果收到后续事件,我希望调用我的代理。
使用Reactive Extensions有一种简单的方法吗?
示例代码:
static void Main(string[] args)
{
Console.WriteLine("Running...");
var generator = Observable
.GenerateWithTime(1, x => x <= 100, x => x, x => TimeSpan.FromMilliseconds(1), x => x + 1)
.Timestamp();
var builder = new StringBuilder();
generator
.Sample(TimeSpan.FromSeconds(1))
.Finally(() => Console.WriteLine(builder.ToString()))
.Subscribe(feed =>
builder.AppendLine(string.Format("Observed {0:000}, generated at {1}, observed at {2}",
feed.Value,
feed.Timestamp.ToString("mm:ss.fff"),
DateTime.Now.ToString("mm:ss.fff"))));
Console.ReadKey();
}
当前输出:
Running...
Observed 064, generated at 41:43.602, observed at 41:43.602
Observed 100, generated at 41:44.165, observed at 41:44.602
但我想观察(时间戳显然会改变)
Running...
Observed 001, generated at 41:43.602, observed at 41:43.602
....
Observed 100, generated at 41:44.165, observed at 41:44.602
答案 0 :(得分:13)
好,
这里有3个场景:
1)我想每秒获得一个事件流值。 意味着:如果它每秒产生更多事件,你将得到一个总是更大的缓冲区。
observableStream.Throttle(timeSpan)
2)我想得到最新的事件,这是在第二次事件发生之前产生的 意思是:其他事件被删除。
observableStream.Sample(TimeSpan.FromSeconds(1))
3)你想得到最后一秒发生的所有事件。那每一秒
observableStream.BufferWithTime(timeSpan)
4)你想要选择在第二个和所有值之间发生的事情,直到第二个已经过去,并且你的结果被返回
observableStream.CombineLatest(Observable.Interval(1000), selectorOnEachEvent)
答案 1 :(得分:13)
以下是我在RX论坛的帮助下得到的结果:
这个想法是为原始序列发出一系列“门票”。这些“门票”在超时时间延迟,不包括第一个“门票”,该门票立即预先加到门票序列中。当一个事件进来并且有一个等待的票时,事件立即触发,否则它等到票然后开火。当它发射时,发出下一张票,依此类推......
要结合故障单和原始事件,我们需要一个组合器。不幸的是,“标准”.CombineLatest不能在这里使用,因为它会触发之前使用过的门票和事件。因此,我必须创建自己的组合器,它基本上是一个过滤的.CombineLatest,仅当组合中的两个元素都是“新鲜”时才会触发 - 之前从未返回过。我称之为.CombineVeryLatest aka .BrokenZip;)
使用.CombineVeryLatest,上述想法可以这样实现:
public static IObservable<T> SampleResponsive<T>(
this IObservable<T> source, TimeSpan delay)
{
return source.Publish(src =>
{
var fire = new Subject<T>();
var whenCanFire = fire
.Select(u => new Unit())
.Delay(delay)
.StartWith(new Unit());
var subscription = src
.CombineVeryLatest(whenCanFire, (x, flag) => x)
.Subscribe(fire);
return fire.Finally(subscription.Dispose);
});
}
public static IObservable<TResult> CombineVeryLatest
<TLeft, TRight, TResult>(this IObservable<TLeft> leftSource,
IObservable<TRight> rightSource, Func<TLeft, TRight, TResult> selector)
{
var ls = leftSource.Select(x => new Used<TLeft>(x));
var rs = rightSource.Select(x => new Used<TRight>(x));
var cmb = ls.CombineLatest(rs, (x, y) => new { x, y });
var fltCmb = cmb
.Where(a => !(a.x.IsUsed || a.y.IsUsed))
.Do(a => { a.x.IsUsed = true; a.y.IsUsed = true; });
return fltCmb.Select(a => selector(a.x.Value, a.y.Value));
}
private class Used<T>
{
internal T Value { get; private set; }
internal bool IsUsed { get; set; }
internal Used(T value)
{
Value = value;
}
}
编辑:这是AndreasKöpf在论坛上提出的另一个更紧凑的CombineVeryLatest变种:
public static IObservable<TResult> CombineVeryLatest
<TLeft, TRight, TResult>(this IObservable<TLeft> leftSource,
IObservable<TRight> rightSource, Func<TLeft, TRight, TResult> selector)
{
return Observable.Defer(() =>
{
int l = -1, r = -1;
return Observable.CombineLatest(
leftSource.Select(Tuple.Create<TLeft, int>),
rightSource.Select(Tuple.Create<TRight, int>),
(x, y) => new { x, y })
.Where(t => t.x.Item2 != l && t.y.Item2 != r)
.Do(t => { l = t.x.Item2; r = t.y.Item2; })
.Select(t => selector(t.x.Item1, t.y.Item1));
});
}
答案 2 :(得分:7)
我昨晚在同样的问题上苦苦挣扎,相信我找到了一个更优雅(或至少更短)的解决方案:
var delay = Observable.Empty<T>().Delay(TimeSpan.FromSeconds(1));
var throttledSource = source.Take(1).Concat(delay).Repeat();
答案 3 :(得分:5)
这是我在Rx forum中发布的对此问题的回答:
<强>更新强>: 这是一个新版本,当事件发生的时间差超过一秒时,不再延迟事件转发:
public static IObservable<T> ThrottleResponsive3<T>(this IObservable<T> source, TimeSpan minInterval)
{
return Observable.CreateWithDisposable<T>(o =>
{
object gate = new Object();
Notification<T> last = null, lastNonTerminal = null;
DateTime referenceTime = DateTime.UtcNow - minInterval;
var delayedReplay = new MutableDisposable();
return new CompositeDisposable(source.Materialize().Subscribe(x =>
{
lock (gate)
{
var elapsed = DateTime.UtcNow - referenceTime;
if (elapsed >= minInterval && delayedReplay.Disposable == null)
{
referenceTime = DateTime.UtcNow;
x.Accept(o);
}
else
{
if (x.Kind == NotificationKind.OnNext)
lastNonTerminal = x;
last = x;
if (delayedReplay.Disposable == null)
{
delayedReplay.Disposable = Scheduler.ThreadPool.Schedule(() =>
{
lock (gate)
{
referenceTime = DateTime.UtcNow;
if (lastNonTerminal != null && lastNonTerminal != last)
lastNonTerminal.Accept(o);
last.Accept(o);
last = lastNonTerminal = null;
delayedReplay.Disposable = null;
}
}, minInterval - elapsed);
}
}
}
}), delayedReplay);
});
}
这是我之前的尝试:
var source = Observable.GenerateWithTime(1,
x => x <= 100, x => x, x => TimeSpan.FromMilliseconds(1), x => x + 1)
.Timestamp();
source.Publish(o =>
o.Take(1).Merge(o.Skip(1).Sample(TimeSpan.FromSeconds(1)))
).Run(x => Console.WriteLine(x));
答案 4 :(得分:2)
好的,这是一个解决方案。我不喜欢它,特别是,但是......好吧。
向Jon指出我的SkipWhile,以及指向BufferRithTime的cRichter。谢谢你们。
static void Main(string[] args)
{
Console.WriteLine("Running...");
var generator = Observable
.GenerateWithTime(1, x => x <= 100, x => x, x => TimeSpan.FromMilliseconds(1), x => x + 1)
.Timestamp();
var bufferedAtOneSec = generator.BufferWithTime(TimeSpan.FromSeconds(1));
var action = new Action<Timestamped<int>>(
feed => Console.WriteLine("Observed {0:000}, generated at {1}, observed at {2}",
feed.Value,
feed.Timestamp.ToString("mm:ss.fff"),
DateTime.Now.ToString("mm:ss.fff")));
var reactImmediately = true;
bufferedAtOneSec.Subscribe(list =>
{
if (list.Count == 0)
{
reactImmediately = true;
}
else
{
action(list.Last());
}
});
generator
.SkipWhile(item => reactImmediately == false)
.Subscribe(feed =>
{
if(reactImmediately)
{
reactImmediately = false;
action(feed);
}
});
Console.ReadKey();
}
答案 5 :(得分:0)
您是否尝试过Throttle
扩展方法?
来自文档:
忽略可观察序列中的值,后面跟着dueTime
之前的另一个值
我不太清楚这是否会做你想做的事情 - 因为你想要忽略下面的值而不是第一个值...但我会期待它成为你想成为的人。试一试:)
编辑:嗯......不,毕竟我认为Throttle
是正确的。我相信我明白你想做什么,但我在框架中看不到任何东西。我可能错过了一些东西。你有没有在Rx论坛上问过?如果它现在不存在,它们可能很乐意添加它:)
我怀疑你可以巧妙地用SkipUntil
和SelectMany
以某种方式做到这一点......但我认为它应该采用自己的方法。
答案 6 :(得分:0)
您要搜索的是CombineLatest。
public static IObservable<TResult> CombineLatest<TLeft, TRight, TResult>(
IObservable<TLeft> leftSource,
IObservable<TRight> rightSource,
Func<TLeft, TRight, TResult> selector
)
当选择器(时间)有值时,合并2个obeservable并返回所有值。
编辑:john是对的,这可能不是首选的解决方案
答案 7 :(得分:0)
受Bluelings的启发,我在这里提供了一个使用Reactive Extensions 2.2.5编译的版本。
此特定版本计算样本数并提供最后一个采样值。为此,使用以下类:
class Sample<T> {
public Sample(T lastValue, Int32 count) {
LastValue = lastValue;
Count = count;
}
public T LastValue { get; private set; }
public Int32 Count { get; private set; }
}
以下是运营商:
public static IObservable<Sample<T>> SampleResponsive<T>(this IObservable<T> source, TimeSpan interval, IScheduler scheduler = null) {
if (source == null)
throw new ArgumentNullException(nameof(source));
return Observable.Create<Sample<T>>(
observer => {
var gate = new Object();
var lastSampleValue = default(T);
var lastSampleTime = default(DateTime);
var sampleCount = 0;
var scheduledTask = new SerialDisposable();
return new CompositeDisposable(
source.Subscribe(
value => {
lock (gate) {
var now = DateTime.UtcNow;
var elapsed = now - lastSampleTime;
if (elapsed >= interval) {
observer.OnNext(new Sample<T>(value, 1));
lastSampleValue = value;
lastSampleTime = now;
sampleCount = 0;
}
else {
if (scheduledTask.Disposable == null) {
scheduledTask.Disposable = (scheduler ?? Scheduler.Default).Schedule(
interval - elapsed,
() => {
lock (gate) {
if (sampleCount > 0) {
lastSampleTime = DateTime.UtcNow;
observer.OnNext(new Sample<T>(lastSampleValue, sampleCount));
sampleCount = 0;
}
scheduledTask.Disposable = null;
}
}
);
}
lastSampleValue = value;
sampleCount += 1;
}
}
},
error => {
if (sampleCount > 0)
observer.OnNext(new Sample<T>(lastSampleValue, sampleCount));
observer.OnError(error);
},
() => {
if (sampleCount > 0)
observer.OnNext(new Sample<T>(lastSampleValue, sampleCount));
observer.OnCompleted();
}
),
scheduledTask
);
}
);
}