使用Reactive Extensions重播带时间戳的事件流

时间:2018-02-18 15:35:19

标签: c# system.reactive

我有以下课程的集合:

public class Event
{
    public DateTimeOffset Timestamp;
    public object Data;
}

我想创建IObservable<Event>,其中每个项目在将来Timestamp时发布。这可能是Observable.Delay还是我必须编写自己的IObservable<T>实现?

我会提到这个结构就像一个日志文件。可能有数以万计的Event个项目,但每秒只能发布1-2个。

3 个答案:

答案 0 :(得分:5)

事实证明,使用Observable.Delay重载变得非常简单:

//given IEnumerable<Event> events:
var observable = events.ToObservable().Delay(ev => Observable.Timer(ev.Timestamp));

答案 1 :(得分:1)

虽然我的第一个答案是按预期工作,但创建可观察序列的性能对于成千上万的事件并不理想 - 您需要支付大量的初始化成本(在我的机器上按10秒的顺序)。

为了提高性能,利用已经排序的数据特性,我实现了自定义IEnumerable<Event>循环事件,在它们之间产生和休眠。使用此IEnumerable,可以轻松调用ToObservable<T>并按预期工作:

IObservable<Event> CreateSimulation(IEnumerable<Event> events)
{
     IEnumerable<Event> simulation()
     {
         foreach(var ev in events)
         {
             var now = DateTime.UtcNow;

             if(ev.Timestamp > now)
             {
                 Thread.Sleep(ev.Timestamp - now);
             }

             yield return ev;          
        }
    }

    return simulation().ToObservable();
}

答案 2 :(得分:0)

似乎Rx库缺少通过延迟枚举和时移其元素将IEnumerable<T>转换为IObservable<T>的机制。下面是一个自定义实现。这个想法是Zip可以与某个主题枚举的源,并通过在适当的时候向该主题发送OnNext通知来控制枚举。

/// <summary>Converts an enumerable sequence to a time shifted observable sequence,
/// based on a time selector function for each element.</summary>
public static IObservable<T> ToObservable<T>(
    this IEnumerable<T> source,
    Func<T, DateTimeOffset> dueTimeSelector,
    IScheduler scheduler = null)
{
    scheduler ??= Scheduler.Default;
    return Observable.Defer(() =>
    {
        var subject = new BehaviorSubject<Unit>(default);
        return subject
            .Zip(source, (_, x) => x)
            .Delay(x => Observable.Timer(dueTimeSelector(x), scheduler))
            .Do(_ => subject.OnNext(default));
    });
}

之所以选择BehaviorSubject是因为它具有初始值,因此可以自然地使车轮运动。

Observable.Defer运算符用于防止多个订阅共享同一状态(在这种情况下为subject),并且彼此干扰。有关此here的更多信息。

用法示例:

IEnumerable<Event> events = GetEvents();

IObservable<Event> observable = events.ToObservable(x => x.Timestamp);