.NET ReactiveExtensions:在可变时间范围内使用Sample()

时间:2019-06-14 12:12:21

标签: system.reactive rx.net

鉴于高频的可观测数据流,我只希望每隔XX秒发出一个项目。

通常在RX中使用.Sample(TimeSpan.FromSeconds(XX))

完成

但是...我希望时间间隔根据数据的某些属性而变化。

假设我的数据是:

班级职位 {   ...   公共int速度; }

如果速度小于100,我想每5秒发射一次数据。如果速度高于100,则应每2秒发送一次。

是否可以使用现成的Sample()还是我需要自己构建一些东西?

2 个答案:

答案 0 :(得分:1)

这是一个底层实现,利用System.Reactive.Concurrency.Scheduler.SchedulePeriodic扩展方法作为计时器。

public static IObservable<TSource> Sample<TSource>(this IObservable<TSource> source,
    Func<TSource, TimeSpan> intervalSelector, IScheduler scheduler = null)
{
    if (source == null) throw new ArgumentNullException(nameof(source));
    if (intervalSelector == null)
        throw new ArgumentNullException(nameof(intervalSelector));
    scheduler = scheduler ?? Scheduler.Default;

    return Observable.Create<TSource>(observer =>
    {
        TimeSpan currentInterval = Timeout.InfiniteTimeSpan;
        IDisposable timer = null;
        TSource latestItem = default;
        bool latestEmitted = true;
        object locker = new object();

        Action periodicAction = () =>
        {
            TSource itemToEmit;
            lock (locker)
            {
                if (latestEmitted) return;
                itemToEmit = latestItem;
                latestItem = default;
                latestEmitted = true;
            }
            observer.OnNext(itemToEmit);
        };

        return source.Subscribe(onNext: item =>
        {
            lock (locker)
            {
                latestItem = item;
                latestEmitted = false;
            }
            var newInterval = intervalSelector(item);
            if (newInterval != currentInterval)
            {
                timer?.Dispose();
                timer = scheduler.SchedulePeriodic(newInterval, periodicAction);
                currentInterval = newInterval;
            }
        }, onError: ex =>
        {
            timer?.Dispose();
            observer.OnError(ex);
        }, onCompleted: () =>
        {
            timer?.Dispose();
            observer.OnCompleted();
        });
    });
}

用法示例:

observable.Sample(x => TimeSpan.FromSeconds(x.Speed < 100 ? 5.0 : 2.0));

每次intervalSelector回调返回不同的时间间隔时,计时器都会重新启动。在极端情况下,每个新项目都会更改间隔,那么自定义运算符的行为将更像内置Throttle,而不是内置Sample

  

Sample不同,Throttle的时间段是一个滑动窗口。每次Throttle收到一个值时,都会重置窗口。 (citation

答案 1 :(得分:0)

让我知道是否可行:

var query =
    source
        .Publish(ss =>
            ss
                .Select(s => s.Speed < 100 ? 5.0 : 2.0)
                .Distinct()
                .Select(x => ss.Sample(TimeSpan.FromSeconds(x))));