问题
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