簡體   English   中英

Cron 可觀察序列

[英]Cron Observable Sequence

我想使用反應式擴展 (RX) 和 NCrontab 創建一個可觀察序列。 該序列將不同於Observable.Timer()類的東西,因為周期和到期時間不是固定的。 閱讀這篇文章后,似乎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)));
}

更新:這些似乎憑經驗工作,但是我添加了一個重載,它需要一個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);
    }
}

看起來像是問題的組合。 首先,我使用的Observable.Generate重載采用Func<int,DateTimeOffset>參數來確定下一次觸發的時間。 我正在根據調度程序的本地時間而不是 Utc 傳入一個新的DateTimeOffset ,這導致新的“DateTimeOffset”發生變化。 請參閱此問題以獲取解釋。 正確的函數如下:

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();
}

我使用了這個沒有使用Cronos調度程序的解決方案:

public static IObservable<DateTimeOffset> ToObservable(this ICronScheduleObservableConfiguration configuration)
{
    Validate(configuration);
    var schedule = configuration.Expression;
    DateTimeOffset? next = null;
    return Observable.Generate(
        DateTimeOffset.Now,
            i => true,
            i => (next = schedule.GetNextOccurrence(i, configuration.TimeZone)) ?? DateTimeOffset.Now,
            i => next,
            i => i
        ).
        Where(i => i.HasValue).
        Select(i => i.Value);
}

public interface ICronScheduleObservableConfiguration :
    IObservableConfiguration
{
    /// <summary>
    /// Cron schedule with format: https://github.com/HangfireIO/Cronos#cron-format
    /// </summary>
    /// <value>Non-empty</value>
    string Schedule { get; }
    /// <summary>
    /// <see cref="Schedule"/> format
    /// </summary>
    CronFormat Format { get; }
    /// <summary>
    /// Parsed <see cref="Schedule"/> using <see cref="Format"/>
    /// </summary>
    /// <value>non-null</value>
    /// <exception cref="CronFormatException">Parsing with <see cref="CronExpression.Parse(string, CronFormat)"/> failed</exception>
    CronExpression Expression { get; }
    /// <summary>
    /// Time zone used for computing schedule times with <see cref="CronExpression.GetNextOccurrence(DateTimeOffset, TimeZoneInfo, bool)"/>
    /// </summary>
    /// <value>non-null</value>
    TimeZoneInfo TimeZone { get; }
}

首先, scheduler.Now.DateTime不會在您的單元測試中使用TestScheduler為您提供實時時間。 它會根據一些預定義的開始時間為您提供虛擬時間。 您可能應該使用AdvanceTo將時鍾初始化為與您的 crontab 測試數據相對應的內容。

對於此示例測試,這可能不是您的問題。 您的問題很可能是您正在以Tick的粒度編寫測試。 這很少奏效。 因為,對於TestScheduler ,當在 Tick t上發生安排另一個動作“立即”執行的預定動作時,下一個動作實際上不會執行,直到 Tick t+1 如果該操作調度另一個操作,則它不會執行直到滴答t+2等。因此,除非您完全了解Generate如何安排其工作,否則您希望避免編寫滴答級別的測試。

而是嘗試以您正在測試的代碼支持的粒度進行測試。 在這種情況下,我認為那是幾分鍾……所以寫你的測試來推進 59 秒,看看什么也沒發生,然后再推進 2 秒,看看你是否達到了你的預期。 或者,更好的是,使用TestScheduler.CreateObserver方法並只前進一次

var scheduler = new TestScheduler();

// set the virtual clock to something that corresponds with my test crontab data
scheduler.AdvanceTo(someDateTimeOffset);

// now your tests...
var cron = "* * * * *";
var observer = scheduler.CreateObserver();

var disp = ObservableCron.Cron(cron, scheduler).Subscribe(observer);

// advance the clock by 61 seconds
scheduler.AdvanceBy(TimeSpan.FromSeconds(61).Ticks);

// check the contents of the observer
Assert.IsTrue(observer.Messages.Count > 0);

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM