反应式编程规范化时间序列值

时间:2017-08-25 08:33:37

标签: c# system.reactive reactive-programming

我有一个远程程序,它通过套接字连接每10毫秒发送一次更新的测量。在我的客户端程序中,我将此套接字包装在一个生成这些测量的observable中。对于我的用例,测量以10毫秒的间隔到达是很重要的。当然,这种情况并没有发生,因为网络延迟使得它在每个消息之前或之后都会到达。

所以我在远程PC上的基本功能就是在套接字连接上发送它的程序。

--是10毫秒

o--o--o--o--o--o--o--o--o--o--...

由于网络延迟,我的客户端会出现这种情况。

o-o---o-o--o---o--o-o--o-o-...

现在,在我的观察中,我希望"正常化"这样它每10毫秒就会再次发出一个值。

--o--o--o--o--o--o--o--o--o--o...

当然这意味着我将不得不引入一个缓冲时间,它将存储值并以10毫秒的间隔发出它们。有没有办法可以实现这个目标?

这是一些测试代码,它将按照我上面描述的方式发出事件。

using System;
using System.Collections.Generic;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Threading.Tasks;
using Microsoft.Reactive.Testing;

public class Program
{
    protected static event EventHandler<EventArgs> CancelEvent;

    private static Random random = new Random();

    private static double GetRandomNumber(double minimum, double maximum)
    { 
        return random.NextDouble() * (maximum - minimum) + minimum;
    }

    public static void Main()
    {
        var completed = false;

        var scheduler = new TestScheduler();

        var observable = Observable
            .Interval(TimeSpan.FromMilliseconds(7.0), scheduler)
            .SelectMany(e => Observable
                .Return(e, scheduler)
                .Delay(TimeSpan.FromMilliseconds(GetRandomNumber(0.0, 6.0)), scheduler)
            )
            .TimeInterval(scheduler)
            .Select(t => t.Interval.Milliseconds);

        var fromEvent = Observable.FromEventPattern<EventArgs>(
            p => CancelEvent += p,
            p => CancelEvent -= p,
            scheduler
        );

        var cancellable = observable.TakeUntil(fromEvent);

        var results = new List<int>();

        using (cancellable.Subscribe(
            results.Add,
            e => { throw new Exception("No exception is planned! {0}", e); },
            () => { completed = true; })
        )
        {
            scheduler.AdvanceBy(TimeSpan.FromSeconds(3.5).Ticks);
            CancelEvent(null, new EventArgs());
            scheduler.AdvanceBy(TimeSpan.FromSeconds(3).Ticks);
        }

        Console.WriteLine("Have I completed indeed? {0}", completed);
        Console.WriteLine("What emit time deltas been registered before cancellation?\n\t{0}", string.Join("ms\n\t", results));
    }
}

1 个答案:

答案 0 :(得分:2)

这在理论上类似于A way to push buffered events in even intervals

该解决方案如下所示:

var source = new Subject<double>();
var bufferTime = TimeSpan.FromMilliseconds(100);
var normalizedSource = source
    .Delay(bufferTime)
    .Drain(x => Observable.Empty<int>().Delay(TimeSpan.FromMilliseconds(10)));

... Drain的定义如下:

public static class ObservableDrainExtensions
{
    public static IObservable<TOut> Drain<TSource, TOut>(this IObservable<TSource> source,
        Func<TSource, IObservable<TOut>> selector)
    {
        return Observable.Defer(() =>
        {
            BehaviorSubject<Unit> queue = new BehaviorSubject<Unit>(new Unit());

            return source
                .Zip(queue, (v, q) => v)
                .SelectMany(v => selector(v)
                    .Do(_ => { }, () => queue.OnNext(new Unit()))
                );
        });
    }
}

但是,我认为你将遇到10毫秒限定符的问题。安排的时间太短了。如果我没记错的话,调度程序会忽略任何小于15毫秒的延迟并立即触发。鉴于此,即使您使用了更大的间隔(我尝试了100毫秒),由于操作系统上下文切换等原因,您将获得一些差异。