我有一个应用程序,在某些时候几乎同时引发了1000个事件。我想要做的是将事件批处理为50个项目的块,并开始每10秒处理一次。在开始新的批处理之前,无需等待批处理完成。
例如:
10:00:00: 10000 new events received
10:00:00: StartProcessing (events.Take(50))
10:00:10: StartProcessing (events.Skip(50).Take(50))
10:00:15: StartProcessing (events.Skip(100).Take(50))
任何想法如何实现这一目标?我认为Reactive Extensions是可行的方法,但其他解决方案也是可以接受的。
我试着从这里开始:
var bufferedItems = eventAsObservable
.Buffer(15)
.Delay(TimeSpan.FromSeconds(5)
但是注意到延迟并没有像我希望的那样有效,而是所有的批次同时开始,但延迟了5秒。
我也测试了Window方法,但我没有发现任何行为上的差异。我认为Window中的TimeSpan实际上意味着“记录在接下来的10秒内发生的每一个事件:
var bufferedItems = eventAsObservable
.Window(TimeSpan.FromSeconds(10), 5)
.SelectMany(x => x)
.Subscribe(DoProcessing);
我正在使用Rx-Main 2.0.20304-beta。
答案 0 :(得分:22)
如果你不想睡觉,你可以这样做:
var tick = Observable.Interval(TimeSpan.FromSeconds(5));
eventAsObservable
.Buffer(50)
.Zip(tick, (res, _) => res)
.Subscribe(DoProcessing);
答案 1 :(得分:1)
试试这个:
var bufferedItems = eventAsObservable
.Buffer(50)
.Do(_ => { Thread.Sleep(10000); });
答案 2 :(得分:1)
这是一个非常难解决的问题。更重要的是,使用 Zip
运算符将 observable 与 Observable.Interval
对齐的诱人想法是错误且危险的低效。 Zip
运算符与非对称 observable 一起使用时的主要问题是,它会缓冲生成速度最快的 observable 的元素,这可能会导致在长期订阅期间分配大量内存。恕我直言,此运算符的使用应仅限于预期在长期内产生相等(或接近相等)数量的元素的成对可观察对象。
Zip
+Observable.Interval
组合的错误行为出现在 Observable.Interval
比源可观察值更快地发出值时。在这种情况下,Observable.Interval
发出的多余值会被缓冲,所以当源 observable 发出下一个元素时,已经有一个缓冲的 Interval
值形成一对,导致违反“最小元素之间的间隔”策略。
以下是自定义 WithInterval
运算符的实现,该运算符在可观察序列的连续元素之间施加最小间隔。然后,此运算符将用于解决此问题的特定问题,即涉及缓冲区而不是单个元素:
/// <summary>Intercepts a minimum interval between consecutive elements of an
/// observable sequence.</summary>
public static IObservable<T> WithInterval<T>(this IObservable<T> source,
TimeSpan interval, IScheduler scheduler = null)
{
return source
.Scan((Observable.Return(0L), (IObservable<T>)null), (state, x) =>
{
var (previousTimer, _) = state;
var timer = (scheduler != null ? Observable.Timer(interval, scheduler)
: Observable.Timer(interval)).PublishLast();
var delayed = previousTimer.Select(_ => x).Finally(() => timer.Connect());
return (timer, delayed);
})
.Select(e => e.Item2)
.Concat();
}
此实现在连续元素之间放置一个 Observable.Timer
。棘手的部分是如何在正确的时刻激活每个计时器。这是通过Publish
对计时器进行处理,并在每个计时器完成时预热 (Connect
) 下一个计时器来实现的。
有了这个运算符,实现一个自定义的 BatchWithInterval
运算符就很简单了:
/// <summary>Projects each element of an observable sequence into consecutive
/// non-overlapping buffers which are produced based on element count information,
/// intercepting a minimum interval between consecutive buffers.</summary>
public static IObservable<IList<T>> BatchWithInterval<T>(this IObservable<T> source,
int count, TimeSpan interval, IScheduler scheduler = null)
{
return source.Buffer(count).WithInterval(interval, scheduler);
}
用法示例:
var subscription = eventAsObservable
.BatchWithInterval(50, TimeSpan.FromSeconds(10))
.Subscribe(DoProcessing);