简体   繁体   English

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

[英]Replay timestamped event stream with Reactive Extensions

I have a collection of items of following class:我有以下类的项目集合:

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

I want to create IObservable<Event> where each item is published at the time of Timestamp in the future.我想创建IObservable<Event> ,其中每个项目都在未来的Timestamp时间发布。 Is this possible with Observable.Delay or do I have to write my own IObservable<T> implementation?这可以通过Observable.Delay还是我必须编写自己的IObservable<T>实现?

I will mention that this structure is something like a log file.我会提到这个结构有点像日志文件。 There can be tens of thousands of Event items, but only 1-2 are to be published per second.可以有数万个Event项,但每秒只能发布 1-2 个。

It turns out it's very simple to do with Observable.Delay overload taking variable time:事实证明,使用Observable.Delay重载花费可变时间非常简单:

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

While my first answer is working as intended, performance of creating the observable sequence is not ideal with hundreds of thousands of events - you pay substantial initialization cost (order of 10 seconds on my machine).虽然我的第一个答案按预期工作,但创建可观察序列的性能对于数十万个事件并不理想 - 您需要支付大量的初始化成本(在我的机器上为 10 秒)。

To improve performance, taking advantage of already sorted nature of my data, I implemented custom IEnumerable<Event> that is looping through events, yielding and sleeping between them.为了提高性能,利用我的数据已经排序的特性,我实现了自定义IEnumerable<Event>循环遍历事件,在它们之间产生和休眠。 With this IEnumerable one can easily call ToObservable<T> and it works as intended:使用这个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();
}

It seems that the Rx library lacks a mechanism for converting an IEnumerable<T> to an IObservable<T> , by enumerating it lazily and time shifting its elements.似乎 Rx 库缺乏将IEnumerable<T>转换为IObservable<T> ,方法是懒惰地枚举它并时移其元素。 Below is a custom implementation.下面是一个自定义实现。 The idea is to Zip the source enumerable with a subject, and control the enumeration by sending OnNext notifications to the subject at the right moments.这个想法是用一个主题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));
    });
}

The BehaviorSubject has been chosen because it has an initial value, and so it sets the wheels in motion naturally.选择BehaviorSubject是因为它有一个初始值,因此它可以自然地设置轮子运动。

The Observable.Defer operator is used to prevent multiple subscriptions from sharing the same state (the subject in this case), and interfering with each other. Observable.Defer操作符用于防止多个订阅共享相同的状态(本例中的subject ),并相互干扰。 More info about this here .更多信息请点击此处

Usage example:用法示例:

IEnumerable<Event> events = GetEvents();

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM