Cron Observable Sequence

时间:2014-10-27 21:44:23

标签: c# timer cron system.reactive observable

我想使用反应式扩展(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);
    }
}

3 个答案:

答案 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