Timer 使用反应式扩展创建多个计时器

Timer 使用反应式扩展创建多个计时器,timer,system.reactive,reactive-programming,Timer,System.reactive,Reactive Programming,我有一个非常简单的类,用来轮询目录中的新文件。它有位置、开始监控该位置的时间以及再次检查的时间间隔(以小时为单位): public class Thing { public string Name {get; set;} public Uri Uri { get; set;} public DateTimeOffset StartTime {get; set;} public double Interval {get; set

我有一个非常简单的类,用来轮询目录中的新文件。它有位置、开始监控该位置的时间以及再次检查的时间间隔(以小时为单位):

public class Thing
{
  public string         Name {get; set;}
  public Uri            Uri { get; set;}
  public DateTimeOffset StartTime {get; set;}
  public double         Interval {get; set;}
}
我不熟悉被动扩展,但我认为它正是适合这项工作的工具。在开始的时候,以及在随后的每个时间间隔,我只想调用一个完成所有繁重工作的web服务——我们将使用不断创新的公共bool DoWorkUri来表示这一点

edit:DoWork是对web服务的调用,它将检查新文件并在必要时移动它们,因此其执行应该是异步的。如果完成,则返回true,否则返回false

如果我有一整套这些东西,事情就会变得复杂。我不知道如何为每个对象创建Observable.Timer,并让它们调用相同的方法


edit2:Observable.TimerDateTimeOffset,Timespan似乎非常适合创建一个IObservable,用于我在这里尝试做的事情。想法

这是我能想到的最清晰的方法:

foreach (var thing in things)
{
    Scheduler.ThreadPool.Schedule(
        thing.StartTime,
        () => {
                DoWork(thing.Uri); 
                Observable.Interval(TimeSpan.FromHours(thing.Interval))
                          .Subscribe(_ => DoWork(thing.Uri)));
              }
}
以下一个更具功能性:

things.ToObservable()
    .SelectMany(t => Observable.Timer(t.StartTime).Select(_ => t))
    .SelectMany(t => Observable.Return(t).Concat(
        Observable.Interval(TimeSpan.FromHours(t.Interval)).Select(_ => t)))
    .Subscribe(t => DoWork(t.Uri));
第一个SelectMany在其预定时间创建一个事物流。第二个SelectMany接受这个流,并创建一个新的流,该流在每个间隔重复。这需要与Observable.Return连接,后者立即生成一个值作为Observable.Interval的第一个值被延迟


*注意,第一种解决方案需要c5,否则会咬到你。

这是我能想到的最清晰的方法:

foreach (var thing in things)
{
    Scheduler.ThreadPool.Schedule(
        thing.StartTime,
        () => {
                DoWork(thing.Uri); 
                Observable.Interval(TimeSpan.FromHours(thing.Interval))
                          .Subscribe(_ => DoWork(thing.Uri)));
              }
}
以下一个更具功能性:

things.ToObservable()
    .SelectMany(t => Observable.Timer(t.StartTime).Select(_ => t))
    .SelectMany(t => Observable.Return(t).Concat(
        Observable.Interval(TimeSpan.FromHours(t.Interval)).Select(_ => t)))
    .Subscribe(t => DoWork(t.Uri));
第一个SelectMany在其预定时间创建一个事物流。第二个SelectMany接受这个流,并创建一个新的流,该流在每个间隔重复。这需要与Observable.Return连接,后者立即生成一个值作为Observable.Interval的第一个值被延迟

*注意,第一种解决方案需要c5,否则会咬到你。

Hmmm。你需要做点什么吗?我想是的。你没说,但我也会假设嫁妆是同步的

things.ToObservable()
  .SelectMany(thing => Observable
    .Timer(thing.StartTime, TimeSpan.FromHours(thing.Interval))
    .Select(_ => new { thing, result = DoWork(thing.Uri) }))
  .Subscribe(x => Console.WriteLine("thing {0} produced result {1}",
                                    x.thing.Name, x.result));
下面是一个带有假设任务DoWorkAsyncUri的版本:

此版本假定DoWorkAsync将在间隔到期之前很久完成并启动一个新实例,因此不会防止为同一个Thing实例运行并发DoWorkAsync。

Hmmm。DoWork是否会产生您需要处理的结果?我想是的。你没说,但我也会假设嫁妆是同步的

things.ToObservable()
  .SelectMany(thing => Observable
    .Timer(thing.StartTime, TimeSpan.FromHours(thing.Interval))
    .Select(_ => new { thing, result = DoWork(thing.Uri) }))
  .Subscribe(x => Console.WriteLine("thing {0} produced result {1}",
                                    x.thing.Name, x.result));
下面是一个带有假设任务DoWorkAsyncUri的版本:


此版本假定DoWorkAsync将在间隔到期之前很久完成并启动一个新实例,因此不防止为同一个Thing实例运行并发DoWorkAsync。

是否需要有多个计时器?我假设,如果你有一个20件物品的集合,那么我们将创建20个计时器,在同一时间点全部开火?在同一个线程/调度程序上

或者你想在每个时期为每件事做嫁妆

i、 e

vs

有很多方法,你可以在未来做的工作

您可以直接使用调度程序来调度要在中完成的工作 将来 您可以使用Observable.Timer创建一个序列,该序列在将来的指定时间内生成一个值。 您可以使用Observable.Interval创建一个序列,该序列在每个指定的时间段之间产生许多值。 因此,这引出了另一个问题。如果您的轮询时间为60秒,并且您的do work功能需要5秒;下次投票是在55秒后还是在60秒后?在这里,一个答案表示您希望使用Rx序列,另一个答案表示您可能希望使用定期计划

下一个问题是,DoWork是否返回值?目前看来似乎没有。在这种情况下,我认为最适合您做的事情是利用假设为rxv2的周期调度器

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());
现在,它将安排每件事情在自己的时间间隔内周期性地进行处理,而不会产生漂移,并提供取消

如果您愿意,我们现在可以将其升级为查询

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

var work = new CompositeDisposable(scheduledWork);
这些查询的问题是我们没有满足StartTime要求。令人恼火的是,ccscheduler.SchedulePeriodic方法没有提供一个重载,也没有一个开始偏移量

然而,Observable.Timer操作符确实提供了这一点。它还将在内部利用非漂移调度功能。要使用Observable.Timer重建查询,我们可以执行以下操作

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));
现在你有了一个很好的界面,可以避免漂移。但是,我认为如果同时调用许多DoWork操作,那么这里的工作是以串行方式完成的

*理想情况下,我会尽量避免像这样的副作用声明,但我不是100%确定你的要求

编辑 看来对DoWork的调用必须是并行的, 所以你需要多做一点。理想情况下,你可以在纽约制作嫁妆,但如果你做不到,我们可以在制作前伪造嫁妆

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?

你需要有很多计时器吗?我假设,如果你有一个20件物品的集合,那么我们将创建20个计时器,在同一时间点全部开火?在同一个线程/调度程序上

或者你想在每个时期为每件事做嫁妆

i、 e

vs

有很多方法,你可以在未来做的工作

您可以直接使用调度程序来调度要在中完成的工作 将来 您可以使用Observable.Timer创建一个序列,该序列在将来的指定时间内生成一个值。 您可以使用Observable.Interval创建一个序列,该序列在每个指定的时间段之间产生许多值。 因此,这引出了另一个问题。如果您的轮询时间为60秒,并且您的do work功能需要5秒;下次投票是在55秒后还是在60秒后?在这里,一个答案表示您希望使用Rx序列,另一个答案表示您可能希望使用定期计划

下一个问题是,DoWork是否返回值?目前看来似乎没有。在这种情况下,我认为最适合您做的事情是利用假设为rxv2的周期调度器

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());
现在,它将安排每件事情在自己的时间间隔内周期性地进行处理,而不会产生漂移,并提供取消

如果您愿意,我们现在可以将其升级为查询

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

var work = new CompositeDisposable(scheduledWork);
这些查询的问题是我们没有满足StartTime要求。令人恼火的是,ccscheduler.SchedulePeriodic方法没有提供一个重载,也没有一个开始偏移量

然而,Observable.Timer操作符确实提供了这一点。它还将在内部利用非漂移调度功能。要使用Observable.Timer重建查询,我们可以执行以下操作

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));
现在你有了一个很好的界面,可以避免漂移。但是,我认为如果同时调用许多DoWork操作,那么这里的工作是以串行方式完成的

*理想情况下,我会尽量避免像这样的副作用声明,但我不是100%确定你的要求

编辑 似乎对DoWork的调用必须是并行的,因此您需要做更多的工作。理想情况下,你可以在纽约制作嫁妆,但如果你做不到,我们可以在制作前伪造嫁妆

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?


DoWork调用web服务来检查文件夹中的文件。如果成功完成,则返回一个布尔值,否则返回false。我将假设DoWork是同步的-DoWork应该在每个时间间隔为每件事情执行一次,但与其他事情无关。事情应该是并行的。。。DoWork调用web服务检查每个文件夹中的文件。然后,您可能希望在对DoWork的调用中引入一些并发性。更好的是,通过返回Task或IObservable使DoWork异步,然后您可以将它们很好地链接在一起。是的,此版本并行运行这些内容,因此如果多个内容间隔重合,可能会有并发的DoWork调用。我同意@LeeCampbell的建议,即如果DoWork正在调用某个web服务,那么如果它是一个异步方法,您可以提高资源利用率。@Brandon我在result=wait-DoWorkAsyncthing.Uri下得到一个红色的波形,带有消息的Uri不能将void分配给匿名类型属性。虽然我确信这是在告诉我一些有用的东西,但如果我知道它是什么,我会后悔的。DoWork调用web服务来检查文件夹中的文件。如果成功完成,则返回一个布尔值,否则返回false。我将假设DoWork是同步的-DoWork应该在每个时间间隔为每件事情执行一次,但与其他事情无关。事情应该是并行的。。。DoWork调用web服务检查每个文件夹中的文件。然后,您可能希望在对DoWork的调用中引入一些并发性。更好的是,通过返回Task或IObservable使DoWork异步,然后您可以将它们很好地链接在一起。是的,此版本并行运行这些内容,因此如果多个内容间隔重合,可能会有并发的DoWork调用。我同意@LeeCampbell的建议,即如果DoWork正在调用某个web服务,那么如果它是一个异步方法,您可以提高资源利用率。@Brandon我在result=wait-DoWorkAsyncthing.Uri下得到一个红色的波形,带有消息的Uri不能将void分配给匿名类型属性。虽然我确信这告诉了我一些有用的东西,但如果我知道它是什么,我会被诅咒的。这是一个奇妙的解释——尽管我还在消化它——DoWork是对web服务的调用。它检查每个文件夹uri中的新文件,并对找到的文件执行操作。。。可怕的,可怕的事情。漂移-补偿下一个间隔的基础上完成之前的工作-是没有必要的。间隔的最频繁时间预计为每半小时。您可以忽略所有间隔,只使用最后一个查询/代码块。其他答案也都很好。我不明白from uu in可观测。计时器行是怎么做的
兴。在函数式编程中,至少在C语言中,下划线字符是一种礼貌的做法,用于指示未使用返回值或输入参数。在这种情况下,不使用它。从语法上讲,这里需要一个变量名。我们需要这条线,因为我们想为每一件“事情”设置一个计时器。计时器序列将产生一个被忽略的值,该值将允许查询链执行Observable.StartWork子句这是一个奇妙的解释-尽管我还在消化它-DoWork是对web服务的调用。它检查每个文件夹uri中的新文件,并对找到的文件执行操作。。。可怕的,可怕的事情。漂移-补偿下一个间隔的基础上完成之前的工作-是没有必要的。间隔的最频繁时间预计为每半小时。您可以忽略所有间隔,只使用最后一个查询/代码块。其他答案也都很好。我不明白from uu in可观测计时器行是如何做任何事情的。在函数式编程中,至少在C语言中,下划线字符是一种礼貌的做法,用于指示未使用返回值或输入参数。在这种情况下,不使用它。从语法上讲,这里需要一个变量名。我们需要这条线,因为我们想为每一件“事情”设置一个计时器。计时器序列将产生一个被忽略的值,该值将允许查询链执行Observable.StartWork子句为什么选择此方法而不是Observable.TimerDateTimeOffset,TimeSpan??它不会生成正确的事件吗?@ScottSEA我刚刚忘记了重载:为什么选择这个方法而不是Observable.TimerDateTimeOffset,TimeSpan??它不会生成正确的事件吗?@ScottSEA我刚刚忘记了重载: