我有一个非常简单的类,我用它来轮询目录中的新文件。它有位置,开始监控该位置的时间,以及何时再次检查的间隔(以小时为单位):
public class Thing
{
public string Name {get; set;}
public Uri Uri { get; set;}
public DateTimeOffset StartTime {get; set;}
public double Interval {get; set;}
}
我是Reactive Extensions的新手,但我认为它正是这里工作的正确工具。在开始时间以及随后的每个时间间隔内,我只想调用一个能够完成所有繁重工作的Web服务 - 我们将使用具有创造力的public bool DoWork(Uri uri)
来代表它。
编辑:DoWork是对Web服务的调用,它将检查新文件并在必要时移动它们,因此它的执行应该是异步的。如果完成则返回true,否则返回false。
如果我拥有这些Thing
的完整集合,事情会变得复杂。我无法解决如何为每个人创建Observable.Timer()
,并让他们都调用相同的方法。
edit2: Observable.Timer(DateTimeOffset,Timespan)似乎非常适合我在这里尝试创建一个IObservable。思考?
答案 0 :(得分:2)
嗯。 DoWork
会产生您需要执行某项操作的结果吗?我会假设的。你没有说,但我也认为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));
这是一个带有假设Task<bool> DoWorkAsync(Uri)
的版本:
things.ToObservable()
.SelectMany(thing => Observable
.Timer(thing.StartTime, TimeSpan.FromHours(thing.Interval))
.SelectMany(Observable.FromAsync(async () =>
new { thing, result = await DoWorkAsync(thing.Uri) })))
.Subscribe(x => Console.WriteLine("thing {0} produced result {1}",
x.thing.Name, x.result));
此版本假定DoWorkAsync
将在间隔到期之前很久完成并启动新实例,因此不会防止同时DoWorkAsync
运行同一个Thing
实例。< / p>
答案 1 :(得分:2)
你需要有很多计时器吗?我假设如果你收集了20件东西,那么我们将在同一时间点制作20个计时器?在同一个线程/调度程序上?
或许您希望DoWork
在每个时期都能找到事情?
即
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);
}
})
将来你可以通过多种方式开展工作。
所以这现在引入另一个问题。如果您的轮询时间为60秒,并且您的工作功能需要5秒;如果下一次轮询在55秒或60秒内发生?这里有一个答案表明您想要使用Rx序列,另一个表示您可能想要使用定期伪装。
接下来的问题是,DoWork会返回一个值吗?目前它看起来不像*。在这种情况下,我认为最合适的事情是利用周期性调度程序(假设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());
现在这将按照自己的间隔定期处理每个事物(没有漂移)并提供取消。
如果您愿意,我们现在可以将其升级为查询
var scheduledWork = from thing in things
select scheduler.SchedulePeriodic(thing, TimeSpan.FromSeconds(thing.Interval), t=>DoWork(t.Uri));
var work = new CompositeDisposable(scheduledWork);
这些查询的问题是我们无法满足StartTime
要求。令人讨厌的是Ccheduler.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的调用必须是并行的,因此您需要做更多的事情。理想情况下,你制作DoWork asnyc,但如果你不能伪造它,直到我们制作它。
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?
答案 2 :(得分:1)
这是我能想到的最清晰的方法:
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));
First SelectMany在预定时间创建一系列事物。第二个SelectMany接受此流并创建一个重复每个间隔的新流。这需要加到Observable.Return
,当Observable.Interval
的第一个值被延迟时,{{1}}立即生成一个值。
*注意,第一个解决方案需要c#5,否则this会咬你。