我的问题是:对于给定的事件序列,我想缓存它们的值,直到流中有暂停。然后,我将批量处理所有缓存数据并清除缓存状态。
这样做的一种天真的方法是(不是工作代码,可能存在一些错误):
struct FlaggedData
{
public EventData Data { get; set; }
public bool Reset { get; set; }
}
...
IObservable<EventData> eventsStream = GetStream();
var resetSignal = new Subject<FlaggedData>();
var flaggedDataStream = eventsStream
.Select(data => new FlaggedData { Data = data })
.Merge(resetSignal)
.Scan(
new List<EventData>(),
(cache, flaggedData) =>
{
if (!flaggedData.Reset())
{
cache.Add(flaggedData.Data);
return cache;
}
return new List<EventData>();
})
.Throttle(SomePeriodOfTime)
.Subscribe(batch =>
{
resetSignal.OnNext(new FlaggedData { Reset = true});
ProcessBatch(batch);
});
所以在这里,在收到任何批处理之后,我请求重置缓存。问题是因为Throttle
缓存中可能存在一些数据(或者我相信),在这种情况下会丢失。
我想要的是一些操作,如:
ScanWithThrottling<TAccumulate, TSource>(
IObservable<TSource> source,
Func<TAccumulate, TSource, TAccumulate> aggregate,
TimeSpan throttlingSpan)
返回一个observable,它会在每次调用其订阅者的OnNext
时重置累计值。
当然,我可以编写自己的扩展,但问题是我是否可以通过标准Rx操作实现相同的效果。
答案 0 :(得分:4)
我认为这里有一个简单的方法。使用Buffer()
根据节流缓冲元素,如下所示:
var buffered = source.Publish(ps =>
ps.Buffer(() => ps.Throttle(SomePeriodOfTime)));
这将缓冲元素,直到存在SomePeriodOfTime的间隙并将它们显示为列表。无需担心&#34;重置&#34;方面,你不会失去元素。
使用Publish
可确保Buffer
和Throttle
可以使用的源事件的单个共享订阅。节流阀是缓冲器关闭功能,提供一个指示应启动新缓冲区的信号。
这是一个可测试的版本 - 我只是在这里转出每个缓冲区的长度并使用Timestamp
添加时间信息,但它只是IList<T>
你得到原始缓冲流。请注意如何将调度程序作为基于时间的操作的参数提供以启用测试。
注意,您需要nuget package rx-testing来运行此示例,以引入Rx测试框架并获取TestScheduler
和ReactiveTest
类型:
void Main()
{
var scenarios = new Scenarios();
scenarios.Scenario1();
}
public class Scenarios : ReactiveTest
{
public void Scenario1()
{
var scheduler = new TestScheduler();
var source = scheduler.CreateHotObservable(
OnNext(100, 1),
OnNext(200, 2),
OnNext(300, 3),
OnNext(800, 4),
OnNext(900, 5),
OnNext(1400, 6),
OnNext(1600, 7),
OnNext(1700, 8),
OnNext(1800, 9));
var duration = TimeSpan.FromTicks(300);
var buffered = source.Publish(ps =>
ps.Buffer(() => ps.Throttle(duration, scheduler)));
buffered.Timestamp(scheduler).Subscribe(
x => Console.WriteLine("Timestamp: {0} Value: {1}",
x.Timestamp.Ticks, x.Value.Count()));
scheduler.Start();
}
}
答案 1 :(得分:2)
基本上,似乎你想要一个缓冲而不是丢弃的Throttle
。
要解决这个问题,让我们考虑Throttle
的工作原理:
您的规范:
因此:
var throttled = source.Publish(p =>
from value in p
// TODO: Add to buffer (side effect)
from _ in Observable.Timer(period, scheduler).TakeUntil(p)
select buffer); // TODO: Create new buffer (side effect + contention)
如果指定的buffer
引入了并发性,请注意竞争条件以及假设scheduler
的可能争用。
如果在Timer
观察到新的p
时value
过去了,那么在天真的实施中,最新的value
可能会泄漏到buffer
在推送buffer
之前创建新的buffer
。这可能是可以接受的,但无论你如何削减它都需要同步。除非您使用时间戳扩展通知或保证所有操作的线程关联性,否则我似乎无法获得您正在寻找的确切语义。
无论如何,作为一个优秀的广义运营商,也许这是有意义的。这似乎正交到我作为一个Rx原语,除非我在我的分析中错了。考虑添加work item。
我还清楚地记得在Rx MSDN论坛中讨论类似的运算符,可能不止一次。可能值得寻找那些讨论。
编辑:我已经改变了我在这个例子中使用同步的想法,因为无论如何,竞争条件在这里是不可避免的,假设scheduler
引入了并发。
未经测试的例子:
public static IObservable<IList<TSource>> ThrottleBuffered<TSource>(
this IObservable<TSource> source,
TimeSpan period,
IScheduler scheduler)
{
return source.Publish(p =>
{
var buffer = new List<TSource>();
return (from _ in p.Do(buffer.Add)
from __ in Observable.Timer(period, scheduler).TakeUntil(p)
.Do(_ => buffer = new List<TSource>())
select buffer);
});
}