接收顺序groupBy(分区流)

时间:2018-08-20 14:59:37

标签: c# reactive-programming system.reactive

我有很多活动:

event.EventTime: 1s-----2s----3s----4s----5s----6s---
stream:          A-B-C--D-----------------E-F---G-H--

一个事件如下:

public class Event
{
  public DateTime EventTime { get; set; }
  public int Value { get; set; }
}

EventTime应该对应于事件到达的时间,但是可能会有一个小的延迟。但是,这些事件不应乱序进行。

现在,当我指定分组间隔(例如1秒)时,我希望将流像这样分组

1s-------2s----3s----4s----5s-----6s---
[A-B-C]--[D]---[ ]---[ ]---[E-F]--[G-H]

(请注意空白间隔)

我尝试使用Buffer,但可悲的是,我需要按EventTime进行分区,而不是System.DateTime.Now。即使有边界,我也需要某种前瞻性,因为当我使用Buffer(2,1)作为边界并比较[0]和[1]时,即使[1]成功破坏了缓冲区,它仍然会被插入变成旧的而不是新的。我也尝试过GroupBy,但是只有在输入流完成后才产生组。永远都不会发生。然后我尝试了一些东西:

var intervalStart = GetIntervalStartLocal(DateTime.Now) + intervalLength;
var intervals = Observable.Timer(intervalStart, intervalLength);
var eventsAsObservables = intervals.GroupJoin<long, Event, long, Event, (DateTime, IObservable<Event>)>(
        data,
        _ => Observable.Never<long>(),
        _ => Observable.Never<Event>(),
        (intervalNumber, events) => {
          var currentIntervalStart = intervalStart + intervalNumber*intervalLength;
          var eventsInInterval = events
            .SkipWhile(e => GetIntervalStartLocal(e.EventTime) < currentIntervalStart)
            .TakeWhile(e => GetIntervalStartLocal(e.EventTime) == currentIntervalStart);
          return (currentIntervalStart, eventsInInterval);
        });

      var eventsForIntervalsAsObservables = eventsAsObservables.SelectMany(g => {
        var lists = g.Item2.Aggregate(new List<Event>(), (es, e) => { es.Add(e); return es; });
        return lists.Select(l => (intervalStart: g.Item1, events: l));
      });

      var task = eventsForIntervalsAsObservables.ForEachAsync(es => System.Console.WriteLine(
        $"=[{es.intervalStart.TimeOfDay}]= " + string.Join("; ", es.events.Select(e => e.EventTime.TimeOfDay))));

await task;

我当时以为我会使用基于值联接的GroupJoin。因此,首先,我将发出间隔时间戳。然后,在GroupJoins resultSelector中,我将使用Event函数从每个GetIntervalStartLocal计算一个匹配间隔(将日期截断为间隔长度)。之后,我将跳过上一个间隔的所有潜在剩余(SkipWile预期间隔比实际从Event计算的要大)。最后,我将在事件计算的间隔符合预期的情况下使用TakeWhile。

但是,在我什至不去SkipWhile和TakeWhile之前肯定有问题,因为resultSelector实际上并不对数据中的所有数据进行操作,但是会忽略某些数据,例如像这样:

event.EventTime: 1s-----2s----3s----4s----5s----6s---
stream:          A---C--D-------------------F-----H--

然后构造(根据它的操作正确地进行):

1s-----2s----3s----4s----5s---6s---
[A-C]--[D]---[ ]---[ ]---[F]--[H]--

我认为我在这里一定做错了很多事,因为基于流事件值对流进行分区并不难。

1 个答案:

答案 0 :(得分:0)

您需要澄清您想要的内容。鉴于此:

time  : 1s-------2s----3s----4s----5s-----6s---
stream: A-B-C----D-----------------E-F----G-H-- (actual)
group : [A-B-C]--[D]---[ ]---[ ]---[E-F]--[G-H] (desired result)

目前尚不清楚“时间”是您的事件时间戳还是实际时间。如果是实际时间,那当然是不可能的:您不能在C到达之前传递ABC列表。如果您指的是事件时间戳记,那么Buffer或也许Window必须知道何时停止,这并不容易。

GroupBy对我有用,如下所示:

var sampleSource = Observable.Interval(TimeSpan.FromMilliseconds(400))
    .Timestamp()
    .Select(t => new Event { EventTime = t.Timestamp.DateTime, Value = (int)t.Value });

sampleSource
    .GroupBy(e => e.EventTime.Ticks / 10000000) //10M ticks per second
    .Dump(); //LinqPad

唯一的问题是每个组没有严格的标准,因此这是一个巨大的内存泄漏。因此,您可以添加计时器以关闭组:

sampleSource
    .GroupBy(e => e.EventTime.Ticks / 10000000) //10M ticks per second
    .Select(g => g.TakeUntil(Observable.Timer(TimeSpan.FromSeconds(2)))) //group closes 2 seconds after opening
    .Dump(); //LinqPad

此结束还使我们可以返回带有.ToList()而不是Observable的列表:

sampleSource
    .GroupBy(e => e.EventTime.Ticks / 10000000) //10M ticks per second
    .SelectMany(g => g.TakeUntil(Observable.Timer(TimeSpan.FromSeconds(2))).ToList())
    .Dump(); //LinqPad