生成具有动态可变时间间隔的事件

时间:2018-02-06 16:55:10

标签: c# timer system.reactive

我有一个以快速(亚毫秒)到达的数值流,我想在屏幕上显示它们的“即时值”,出于可用性原因,我应该对该流进行缩减采样,使用更新最后一个值可配置时间间隔。该配置将通过拖动滑块按用户首选项完成。

所以我想做的是将源流的最后一个值存储在一个变量中,并有一个自动重新触发计时器,用该变量的值更新显示的值。

我想使用RX,就像这样:

Observable.FromEventPattern<double>(_source, "NewValue")
          .Sample(TimeSpan.FromMilliseconds(100))
          .Subscribe(ep => instantVariable = ep.EventArgs)

问题是,就我所知,我不能动态更改间隔。

我可以想象有很多方法可以使用计时器,但我更喜欢使用RX。

4 个答案:

答案 0 :(得分:3)

假设您可以将样本大小更改建模为可观察的,您可以这样做:

IObservable<int> sampleSizeObservable;

var result = sampleSizeObservable
    .Select(i => Observable.FromEventPattern<double>(_source, "NewValue")
        .Sample(TimeSpan.FromMilliseconds(i))
    )
    .Switch();

Switch基本上可以做你想要的,但是通过Rx。它不会“改变”间隔:Observable(通常)应该是不可变的。相反,只要样本大小发生变化,它就会创建一个新的可观察样本,订阅新的observable,将订阅丢弃到旧的,并将这两个订阅合并在一起,使其看起来与客户订阅者无缝。

答案 1 :(得分:1)

这是一个自定义Sample运算符,其时间间隔可以在订阅生命周期之前或期间随时更改。

/// <summary>Samples the source observable sequence at a dynamic interval
/// controlled by a delegate.</summary>
public static IObservable<T> Sample<T>(this IObservable<T> source,
    out Action<TimeSpan> setInterval)
{
    var intervalController = new ReplaySubject<TimeSpan>(1);
    setInterval = interval => intervalController.OnNext(interval);

    return source.Publish(shared => intervalController
        .Select(timeSpan => timeSpan == Timeout.InfiniteTimeSpan ?
            Observable.Empty<long>() : Observable.Interval(timeSpan))
        .Switch()
        .WithLatestFrom(shared, (_, x) => x)
        .TakeUntil(shared.LastOrDefaultAsync()));
}

out Action<TimeSpan> setInterval参数是控制采样间隔的机制。可以使用任何非负TimeSpan参数或具有暂停采样作用的特殊值Timeout.InfiniteTimeSpan来调用它。

在源序列产生的值比期望的采样间隔慢的情况下,此运算符与内置的Sample不同。内置的Sample将采样调整为信号源的速度,而不会重复发射相同的值。相反,此运算符会保持自己的速度,从而可以多次发出相同的值。如果不希望出现这种情况,可以在DistinctUntilChanged之后附加Sample运算符。

用法示例:

var subscription = Observable.FromEventPattern<double>(_source, "NewValue")
    .Sample(out var setSampleInterval)
    .Subscribe(ep => instantVariable = ep.EventArgs);

setSampleInterval(TimeSpan.FromMilliseconds(100)); // Initial sampling interval

//...

setSampleInterval(TimeSpan.FromMilliseconds(500)); // Slow down

//...

setSampleInterval(Timeout.InfiniteTimeSpan); // Suspend

答案 2 :(得分:0)

如果数据进入&#34;亚毫秒&#34; intervalls,你需要处理的是realtime programming。使用.NET Framework的垃圾收集 - 与使用C#的大多数其他东西 - 相距甚远。你可以在某些地区靠近。但是你永远无法保证程序能够跟上数据的摄取量。

除此之外,你想要的听起来像速率限制代码。 Interval不会经常运行的代码。我为多线程示例编写了此示例代码,但它应该让您开始使用Idea:

integer interval = 20;
DateTime dueTime = DateTime.Now.AddMillisconds(interval);

while(true){
  if(DateTime.Now >= dueTime){
    //insert code here

    //Update next dueTime
    dueTime = DateTime.Now.AddMillisconds(interval);
  }
  else{
    //Just yield to not tax out the CPU
    Thread.Sleep(1);
  }
}

请注意,DateTime实际上具有有限的精度,通常不会低于5-20毫秒。秒表更精确。但老实说,每秒超过60次更新(17 ms Intervall)的任何内容都不会是人类可读的。

另一个问题实际上是编写GUI非常昂贵。如果每个用户触发的事件只写一次,您将永远不会注意到。但是,如果您从循环中发送更新(包括在另一个线程中运行的更新),您可以快速解决问题。在我第一次使用多线程测试的时候,我实际上做了这么多复杂的进度报告,我最终简单地重载了用线程来改变GUI线程。

答案 3 :(得分:0)

尝试这个,让我知道它是否有效:

Observable
    .FromEventPattern<double>(_source, "NewValue")
    .Window(() => Observable.Timer(TimeSpan.FromMilliseconds(100)))
    .SelectMany(x => x.LastAsync());