开发者

Time-varying values in Rx

开发者 https://www.devze.com 2023-04-07 07:30 出处:网络
I have two observables. One i开发者_开发技巧s from Observable.fromEvent(..), where the underlying event is the user checking a Winforms checkbox. The other is Observable.Interval(..) which I subscribe

I have two observables. One i开发者_开发技巧s from Observable.fromEvent(..), where the underlying event is the user checking a Winforms checkbox. The other is Observable.Interval(..) which I subscribe to in order to do some IO, and I would like to prevent this observable from doing IO, whenever the checkbox is not checked.

I could do it like this:

var gui = new GUI();

var booleans = Observable
    .FromEvent<GUI.NewAllowHandler, bool>(
        h => gui.NewAllow += h,
        h => gui.NewAllow -= h)

Observable.Interval(TimeSpan.FromSeconds(10))
          .CombineLatest(booleans, Tuple.Create)
          .Where(t => t.Item2)
          .Select(t => t.Item1)
          .Subscribe(l => DoStuff(l));

but this has the overhead of mixing the booleans in and out of the stream. A nicer way of doing this would be, if I could generate a time-varying value from the booleans variable, which at all times had the value of the last event. Then I could do something like this:

var gui = new GUI();

var booleanState = Observable              // typeof(booleanState) == ???
    .FromEvent<GUI.NewAllowHandler, bool>(
        h => gui.NewAllow += h,
        h => gui.NewAllow -= h)
    .TimeValue()                           // hypothetical syntax


Observable.Interval(TimeSpan.FromSeconds(10))
          .Where(_ => booleanState) 
          .Subscribe(l => DoStuff(l));

, which to me seems much closer to the problem statement. Is there anything like this in Rx, or is there anything else, that could make such problems easier to handle?


The Where statement in your interval should work with a properly scoped normal bool:

var booleans = Observable
    .FromEvent<GUI.NewAllowHandler, bool>(
        h => gui.NewAllow += h,
        h => gui.NewAllow -= h)

var isBoxChecked = false;
booleans.Subscribe(t => isBoxChecked = t);

Observable.Interval(TimeSpan.FromSeconds(10))
    .Where(_ => isBoxChecked)
    .Subscribe(l => DoStuff(l))

Edit: Per your comment, another way of doing it:

intervals = Observable.Interval(TimeSpan.FromSeconds(10));

booleans
    .Where(t => t)
    .SelectMany(_ => intervals.TakeUntil(booleans))
    .Subscribe(l => DoStuff(l))


You need to model the checkbox checked state as Behavior and not as Event stream (because behavior has always a value and this value changes over a period of time - which fits with checkbox checked state). So you can do something like:

var booleans = new BehaviorSubject<bool>(chk.Checked)
var chkEvents = ... //generate boolean observable from checkbox check event
chkEvents.Subscribe(booleans);

Observable.Interval(TimeSpan.FromSeconds(10))
 .Where(i => booleans.First())
 .Subscribe(i => DoIO());


I'm going to give you two solutions. The first is a very simple and hopefully obvious one using only one observable. The second is a uses both observables.

Since you want to allow the IO only when the box is checked then this is the simplest approach:

Observable
    .Interval(TimeSpan.FromSeconds(10))
    .Where(_ => gui.IsChecked)
    .Subscribe(l => DoStuff(l));

No need at all for the other observable.

But if you really need to use it then the Switch() extension method is your best bet. Try this:

booleans
    .Select(b => b == true
        ? Observable.Interval(TimeSpan.FromSeconds(10)) 
        : Observable.Empty<long>())
    .Switch()
    .Subscribe(l => DoStuff(l));

It's pretty clean and helps to show that there are empty periods if the checkbox is not ticked.

I hope this helps.

0

精彩评论

暂无评论...
验证码 换一张
取 消