Generate events with dynamically changeable time interval

ぐ巨炮叔叔 提交于 2021-02-10 06:23:10

问题


I have a stream of numeric values that arrive at a fast rate (sub-millisecond), and I want to display their "instant value" on screen, and for usability reasons I should downsample that stream, updating the last value using a configurable time interval. That configuration would be done by user preference, via dragging a slider.

So what I want to do is to store the last value of the source stream in a variable, and have an auto-retriggering timer that updates the displayed value with that variable's value.

I think about using RX, something like this:

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

The problem is that I cannot, as far as I know, dynamically change the interval.

I can imagine there are ways to do it using timers, but I would prefer to use RX.


回答1:


Assuming you can model the sample-size changes as an observable, you can do this:

IObservable<int> sampleSizeObservable;

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

Switch basically does what you want, but via Rx. It doesn't "change" the interval: Observables are (generally) supposed to be immutable. Rather whenever the sample size changes, it creates a new sampling observable, subscribes to the new observable, drops the subscription to the old one, and melds those two subscriptions together so it looks seamless to a client subscriber.




回答2:


Here is a custom Sample operator with an interval that can be changed at any time before or during the lifetime of a subscription.

/// <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()));
}

The out Action<TimeSpan> setInterval parameter is the mechanism that controls the interval of the sampling. It can be invoked with any non-negative TimeSpan argument, or with the special value Timeout.InfiniteTimeSpan that has the effect of suspending the sampling.

This operator defers from the built-in Sample in the case where the source sequence produces values slower than the desirable sampling interval. The built-in Sample adjusts the sampling to the tempo of the source, never emitting the same value twice. On the contrary this operator maintains its own tempo, making it possible to emit the same value more than once. In case this is undesirable, you can attach the DistinctUntilChanged operator after the Sample.

Usage example:

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



回答3:


Try this one and let me know if it works:

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



回答4:


If the data comes in in "Sub-millisecond" intervalls, what you would need to handle it is realtime programming. The Garbage Collection using .NET Framework - as most other things using C# - are a far step away from that. You can maybe get close in some areas. But you can never guarantee remotely that the Programm will be able to keep up with that data intake.

Aside from that, what you want sounds like Rate Limiting code. Code that will not run more often then Interval. I wrote this example code for a Multithreading example, but it should get you started on the 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);
  }
}

Note that DateTime actually has limited Precision, often not going lower then 5-20 ms. The Stop watch is a lot more precise. But honestly, anything beyond 60 updates per Second (17 ms Intervall) will propably not be human readable.

Another issue issue is actually that writing teh GUI is costly. You will never notice if you only write once per user triggered event. But onec you send updates from a loop (inlcuding one running in another thread) you can quickly into issues. In my first test with Multithreading I actually did so much and so complicated progress reporting, I ended up plain overloading the GUI thread with Stuff to change.



来源:https://stackoverflow.com/questions/48648135/generate-events-with-dynamically-changeable-time-interval

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!