我想使用反应式扩展(RX)和NCrontab创建一个可观察的序列。序列与Observable.Timer()
之类的不同之处在于句点和到期时间不固定。阅读this article后,似乎Observable.Generate()
是可行的方法。我正在考虑两种变体:一种在边界内运行,另一种在永久运行。这些实施有意义吗?
public static IObservable<DateTime> Cron(string cron)
{
var schedule = CrontabSchedule.Parse(cron);
return Observable.Generate(DateTime.Now, d=>true, d => DateTime.Now, d => d,
d => new DateTimeOffset(schedule.GetNextOccurrence(d)));
}
public static IObservable<DateTime> Cron(string cron, DateTime start, DateTime end)
{
var schedule = CrontabSchedule.Parse(cron);
return Observable.Generate(start, d => d < end, d => DateTime.Now, d => d,
d => new DateTimeOffset(schedule.GetNextOccurrence(d)));
}
update :这些似乎是凭经验工作的,但是我添加了一个带有IScheduler
的重载,似乎无法在单元测试中触发序列。我使用TestScheduler
错了还是函数实现有问题?
public static IObservable<int> Cron(string cron, IScheduler scheduler)
{
var schedule = CrontabSchedule.Parse(cron);
return Observable.Generate(0, d => true, d => d + 1, d => d,
d => new DateTimeOffset(schedule.GetNextOccurrence(scheduler.Now.DateTime)), scheduler);
}
[TestClass]
public class EngineTests
{
[TestMethod]
public void TestCron()
{
var scheduler = new TestScheduler();
var cron = "* * * * *";
var values = new List<int>();
var disp = ObservableCron.Cron(cron, scheduler).Subscribe(values.Add);
scheduler.AdvanceBy(TimeSpan.TicksPerMinute - 1);
scheduler.AdvanceBy(1);
scheduler.AdvanceBy(1);
Assert.IsTrue(values.Count> 0);
}
}
答案 0 :(得分:3)
它看起来像是一系列问题。首先,我使用的Observable.Generate
重载采用Func<int,DateTimeOffset>
参数来确定下次触发时间。我根据调度程序的本地时间而不是Utc传递了一个新的DateTimeOffset
,这导致新的&#39; DateTimeOffset`移位。有关说明,请参阅this question。正确的功能如下:
public static IObservable<int> Cron(string cron, IScheduler scheduler)
{
var schedule = CrontabSchedule.Parse(cron);
return Observable.Generate(0, d => true, d => d + 1, d => d,
d => new DateTimeOffset(schedule.GetNextOccurrence(scheduler.Now.UtcDateTime)), scheduler);
}
就测试而言,我想出了一些能够更好地展示意图的东西:
[TestMethod]
public void TestCronInterval()
{
var scheduler = new TestScheduler();
var end = scheduler.Now.UtcDateTime.AddMinutes(10);
const string cron = "*/5 * * * *";
var i = 0;
var seconds = 0;
var sub = ObservableCron.Cron(cron, scheduler).Subscribe(x => i++);
while (i < 2)
{
seconds++;
scheduler.AdvanceBy(TimeSpan.TicksPerSecond);
}
Assert.IsTrue(seconds == 600);
Assert.AreEqual(end, scheduler.Now.UtcDateTime);
sub.Dispose();
}
答案 1 :(得分:1)
我在没有使用Cronos的调度程序的情况下使用了此解决方案:
Input DataFrame:
P T W A1 A2 S1 S2
10 1 1 10 20 A B
10 1 2 11 25 C C
10 1 3 10 15 D D
10 1 4 9 10 C C
10 1 5 8 5 C C
10 2 1 20 40 B A
10 2 2 10 10 C C
10 2 3 15 30 C C
10 2 4 5 20 D D
10 2 5 25 10 C C
答案 2 :(得分:0)
首先,scheduler.Now.DateTime
不会在TestScheduler
的单元测试中为您提供真实的次。它将根据一些预定义的开始时间为您提供虚拟时间。您应该使用AdvanceTo
将时钟初始化为与crontab测试数据相对应的内容。
对于此示例测试,这可能不是您的问题。您的问题很可能是您以Tick
的粒度编写测试。这很少奏效。因为,对于TestScheduler
,当预定行动发生在Tick t
上时,其安排另一个行动立即执行&#34;该下一个行动实际上不会执行直到Tick { {1}}。如果该操作安排了另一个操作,那么它将不会执行直到勾选t+1
等等。因此,除非您完全理解t+2
如何安排其工作,否则您希望避免编写刻度级别测试
而是尝试以您测试的代码支持的粒度进行测试。在这种情况下,我认为这是几分钟...所以写你的测试提前59秒,看到没有发生任何事情,然后提前2秒,看看你是否得到了你的预期。或者,更好的是,使用Generate
方法,只需前进一次
TestScheduler.CreateObserver