Reactive Extensions:批量处理事件+在每批之间添加延迟

时间:2012-06-07 07:16:22

标签: c# system.reactive windows-runtime

我有一个应用程序,在某些时候几乎同时引发了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。

3 个答案:

答案 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);