Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to control a timer using Reactive Extensions (C#) without any side-effects?

I'm trying to implement the following scenario:

  1. A button is pressed down.
  2. An interval is calculated by calling the GetInterval() method.
  3. The DoSomething() method is called immediately.
  4. The DoSomething() method will be called repeatedly every interval milliseconds until the button is released.
  5. When the button is pressed again, go to 2.

It could be implemented as follows:

Timer timer = new Timer();
timer.Tick += DoSomething();

//keyDown and keyUp are IObservables created from the button's events
keyDown.Do(_ =>
{
    timer.Interval = GetInterval();
    timer.Start();
    DoSomething();                
});

keyUp.Do(_ =>
{
    timer.Stop();
});

I know this code have some problems, but it does not matter. What I'm trying to achieve is to implement this scenario using pure Reactive Extensions, without any external timers and side effects (other than calling the DoSomething() method).

I know of Observable.Timer, but I do not know how to use it in this scenario.

EDIT: I can create an IObservable<int> containing current values of the interval. It will probably help.

like image 439
tearvisus Avatar asked Dec 19 '22 22:12

tearvisus


2 Answers

I'm going to suggest that an approach using Select/Switch is going to be better than an approach using SelectMany.

In general, when using SelectMany you may end up with a memory leak as more and more underlying subscriptions are created. This may not happen in this case due to the TakeUntil, but it is worth getting into the habit of avoiding this.

Here's my code:

var subscription =
    keyDown
        .Select(kd =>
            Observable
                .Interval(TimeSpan.FromMilliseconds(500))
                .TakeUntil(keyUp)
                .StartWith(-1L))
        .Switch()
        .Subscribe(n => DoSomething());

When using the Switch you must construct an IObservable<IObservable<T>> to call .Switch() on which takes the latest IObservable<T> produced by the IObservable<IObservable<T>> and flattens it similar to a SelectMany but the former disposes of the previous observable and the latter doesn't.

I've also included a StartWith that ensures that the sequence immediately produces a value.

like image 95
Enigmativity Avatar answered Dec 21 '22 12:12

Enigmativity


keyDown
   .SelectMany(_ => 
       Observable.Interval(TimeSpan.FromMilliseconds(500))
          .TakeUntil(keyUp))
   .Subscribe(_ => DoSomething());

This will start an interval on every keydown event. The timer will complete on a keyup event being emited.

like image 44
James Hay Avatar answered Dec 21 '22 11:12

James Hay