Reactive extension Timer/Interval reset

前端 未结 3 1745
不思量自难忘°
不思量自难忘° 2020-12-18 10:20

I have a project where I need to send a status message every 10 seconds unless there\'s been an update in the meantime. Meaning, every time there would be an update, the tim

相关标签:
3条回答
  • 2020-12-18 10:42

    This is pretty easy to implement using standard Rx operators.

    What isn't clear from your question is exactly what an "update" is. I'm going to assume that you have some sort of observable that fires for every update or that you can create a subject that you'll call .OnNext(...) when there is an update. Without observable updates it is hard to know when to reset the timer.

    So here's the code:

    var update = new Subject<bool>();
    
    var res =
        update
            .Select(x => Observable.Interval(TimeSpan.FromSeconds(10.0)))
            .Switch();
    
    res
        .Subscribe(_ => Console.WriteLine("Status sent."));
    
    update.OnNext(true);
    

    The res query now waits until it gets a value from update and then it selects a new Observable.Interval. This means that after the Select the type is an IObservable<IObservable<long>>, so the .Switch() is required to turn it in to a IObservable<long>. .Switch() does this by only passing out values from the latest observed observable and disposing of any previous observables. In other words, for each update a new timer is started and the previous timer is cancelled. This means that if you have updates occurring more frequently than 10 seconds then the timer will never fire.

    Now, if the res observable is an update in its own right, then you can do this:

    res
        .Subscribe(_ =>
        {
            update.OnNext(true);
            Console.WriteLine("Status sent.");
        });
    

    That's fine - it still works, but for each timer firing res will create a new timer. It will mean that anything relying on your update observable/subject will still function correctly.

    0 讨论(0)
  • 2020-12-18 10:51

    I think you could also get away with using Throttle here. The purpose of Throttle is not to let elements though if another element is received within the given timespan. So in your case if an update message is received within 10 secs then don't send a status. See unit test below which uses 200 ticks as the throttle period.

    [TestMethod]
        public void Publish_Status_If_Nothing_Receieved()
        {
            //Arrange
            var scheduler = new TestScheduler();
            var statusObserver = scheduler.CreateObserver<Unit>();
            var updateStream = scheduler.CreateColdObservable(OnNext(100, 1), OnNext(200, 2), OnNext(600, 3),
                OnNext(700, 4));
    
            var waitTime = TimeSpan.FromTicks(200);
    
            //Act
            updateStream.Throttle(waitTime, scheduler)
                .Select(_ => Unit.Default)
                .Subscribe(statusObserver);
    
            //Verify no status received
            scheduler.AdvanceTo(100);
            Assert.AreEqual(0, statusObserver.Messages.Count);
    
            //Verify no status received
            scheduler.AdvanceTo(200);
            Assert.AreEqual(0, statusObserver.Messages.Count);
    
            //Assert status received
            scheduler.AdvanceTo(400);
            statusObserver.Messages.AssertEqual(OnNext(400, Unit.Default));
    
            //Verify no more status received
            scheduler.AdvanceTo(700);
            statusObserver.Messages.AssertEqual(OnNext(400, Unit.Default));
        }
    
    0 讨论(0)
  • 2020-12-18 11:04

    I keep this little helper method with me:

    public static IObservable<long> CreateAutoResetInterval<TSource>(IObservable<TSource> resetter, TimeSpan timeSpan, bool immediate = false)
    {
        return resetter.Select(_ => immediate ? Observable.Interval(timeSpan).StartWith(0) : Observable.Interval(timeSpan)).Switch();
    }
    

    It's basically the same mechanism as Enigmativity's answer

    0 讨论(0)
提交回复
热议问题