Creating Multiple Timers with Reactive Extensions

后端 未结 3 1459
庸人自扰
庸人自扰 2021-01-16 14:06

I\'ve got a very simple class that I am using to poll a directory for new files. It\'s got the location, a time to start monitoring that location, and an interval (in hours

3条回答
  •  自闭症患者
    2021-01-16 14:29

    Do you need there to be many timers? I assume if you have a collection of 20 things, then we will create 20 timers all to fire at the same point in time? On the same thread/scheduler?

    Or perhaps you want to DoWork foreach thing at every period?

    i.e.

    from thing in things
    from x in Observable.Interval(thing.Interval)
    select DoWork(thing.Uri)
    

    vs.

    Observable.Interval(interval)
    .Select(_=>
        {
            foreach(var thing in Things)
            {
                DoWork(thing);
            }
        })
    

    There are many ways that you can do work in the future.

    • You can use a Scheduler directly to schedule work to be done in the future.
    • You can use Observable.Timer to have a sequence that produces one value in the specified time in the future.
    • You can use Observable.Interval to have a sequence that produces many values each the specified time period apart.

    So this now introduces another question. If you have your polling time as 60seconds and you do work function takes 5 seconds; should the next poll happen in 55 seconds or in 60 seconds? Here one answer indicates you want to use an Rx sequence, the other indicates that you probably want to use Periodic Scheudling.

    Next question is, does DoWork return a value? Currently it looks like it does not*. In this case I think the most appropriate thing for you to do is to leverage the Periodic Schedulers (assuming Rx v2).

    var things = new []{
        new Thing{Name="google", Uri = new Uri("http://google.com"), StartTime=DateTimeOffset.Now.AddSeconds(1), Interval=3},
        new Thing{Name="bing", Uri = new Uri("http://bing.com"), StartTime=DateTimeOffset.Now.AddSeconds(1), Interval=3}
    };
    var scheduler = Scheduler.Default;
    var scheduledWork = new CompositeDisposable();
    
    foreach (var thing in things)
    {
        scheduledWork.Add( scheduler.SchedulePeriodic(thing, TimeSpan.FromSeconds(thing.Interval), t=>DoWork(t.Uri)));
    }
    
    //Just showing that I can cancel this i.e. clean up my resources.
    scheduler.Schedule(TimeSpan.FromSeconds(10), ()=>scheduledWork.Dispose());
    

    This now will schedule each thing to be processed periodically (without drift), on it's own interval and provide cancellation.

    We can now upgrade this to a query if you like

    var scheduledWork = from thing in things
                        select scheduler.SchedulePeriodic(thing, TimeSpan.FromSeconds(thing.Interval), t=>DoWork(t.Uri));
    
    var work = new CompositeDisposable(scheduledWork);
    

    The problem with these query is that we don't fulfil the StartTime requirement. Annoyingly the Ccheduler.SchedulePeriodic method does not provide an overload to also have a start offset.

    The Observable.Timer operator does however provide this. It will also internally leverage the non-drifting scheduling features. To reconstruct the query with Observable.Timer we can do the following.

    var urisToPoll = from thing in things.ToObservable()
                     from _ in Observable.Timer(thing.StartTime, TimeSpan.FromSeconds(thing.Interval))
                     select thing;
    
    var subscription = urisToPoll.Subscribe(t=>DoWork(t.Uri));
    

    So now you have a nice interface that should avoid drift. However, I think the work here is done in a serial manner (if many DoWork actions are called concurrently).

    *ideally I would try to avoid side effect statements like this but I am not 100% sure what your requirements.

    EDIT It appears that the calls to DoWork must be in parallel, so then you need to do a bit more. Ideally you make DoWork asnyc, but if you cant we can fake it till we make it.

    var polling = from thing in things.ToObservable()
                  from _ in Observable.Timer(thing.StartTime, TimeSpan.FromSeconds(thing.Interval))
                  from result in Observable.Start(()=>DoWork(thing.Uri))
                  select result;
    
    var subscription = polling.Subscribe(); //Ignore the bool results?
    

提交回复
热议问题