0

出于测试原因,我希望能够调整 Quartz.Net 当前认为的时间,因此我不必等待数小时、数天或数周来检查我的代码是否正常工作。

为此,我创建了以下简单函数(它在 F# 中,但可以用 C# 或其他语言轻松完成):

let SimulateTime = fun () ->
    currentTime <- DateTimeOffset.UtcNow
    timeDifferenceInSeconds <- (currentTime - lastCheckedTime).TotalSeconds
    simulatedTime <- simulatedTime.AddSeconds((timeDifferenceInSeconds *scaleTimeBy))
    lastCheckedTime <- currentTime
    simulatedTime

currentTime、lastCheckedTime 和simulatedTime 都属于DateTimeOffset 类型,而timeDifferenceInSeconds 和scaleTimeBy 都是float 类型。

然后我将 SystemTime.Now 和 SystemTime.UtcNow 更改为使用上述函数,如下所示:

SystemTime.Now <- 
    Func<DateTimeOffset>(
        fun () -> SimulateTime())
SystemTime.UtcNow <- 
    Func<DateTimeOffset>(
        fun () -> SimulateTime())

Mark Seemann 在我的上一个问题中展示了这一点,可以在这里找到。

现在这主要是有效的,只是似乎较长的功能导致它偏离了相当大的幅度。我的意思是我所有的触发器都会失火。例如,如果我将触发器设置为每小时发生一次,并将 scaleTimeBy 设置为 60.0,以便通过的每一秒都计为一分钟,那么它永远不会真正按时触发。如果我有一个失火策略,那么触发器可以关闭,但它列出的激活时间将迟到半小时(因此比本示例中应该慢了整整 30 秒)。

但是我可以这样做:

Console.WriteLine(SimulateTime())
Thread.Sleep(TimeSpan.FromSeconds(60.0))
Console.WriteLine(SimulateTime())

在这个例子中,两次输出到屏幕上的时间差正好是一个小时,所以调用似乎不应该比它增加那么多的时间差。

有人对如何解决此问题或处理此问题的更好方法有任何建议吗?

编辑:所以 SimulateTime 函数的 C# 版本将是这样的:

public DateTimeOffset SimulateTime() {
    currentTime = DateTimeOffset.UtcNow;
    double timeDifference = (currentTime - lastCheckedTime).TotalSeconds;
    simulatedTime = simulatedTime.AddSeconds(timeDifference * scaleTimeBy);
    lastCheckedTime = currentTime
    return simulatedTime;}

如果这可以帮助任何人解决这个问题。

4

1 回答 1

1

所以这个问题是由于 Quartz.net 将空闲并等待它认为它不会很快发生任何触发器以避免进行太多调用而导致的失败。默认情况下,如果在时间跨度内没有发生任何触发器,它会等待大约 30 秒。idleWaitTime 变量是在 QuartzSchedulerThread 中设置的时间跨度。现在,当检查可能很快发生的触发器时,它还使用 QuartzSchedulerResources 中的 BatchTimeWINdow。

idleWaitTime 和 BatchTimeWindow 都可以在配置/属性文件中设置,它们被称为“org.quartz.scheduler.idleWaitTime”和“org.quartz.scheduler.batchTriggerAcquisitionFireAheadTimeWindow”。

基于它在 BatchTimeWindow 中所称的内容,我认为它只是对获取变量的一点展望(因为如果我加快速度,我想要一个小的 idleWaitTime 但我希望它看起来更远提前触发,因为你等待的几秒钟实际上是几分钟,所以会比它想象的更早触发),但是在配置属性的页面上对“org.quartz.scheduler.batchTriggerAcquisitionFireAheadTimeWindow”的描述意味着它可能导致事情提前触发并且不太准确。所以从这里开始是只修改 idleWaitTime 的代码

let threadpool = Quartz.Simpl.SimpleThreadPool()
let jobstore = Quartz.Simpl.RAMJobStore()
let idleWaitTime = TimeSpan.FromSeconds(30.0/scaleTimeBy)
let dbfailureretryinverval = TimeSpan(int64 15000)
Quartz.Impl.DirectSchedulerFactory.Instance.CreateScheduler("TestScheduler","TestInstance",threadpool,jobstore,idleWaitTime,dbfailureretryinverval)
let scheduler = Quartz.Impl.DirectSchedulerFactory.Instance.GetScheduler("TestScheduler")

您可以使用 DirectSchedulerFactory 创建一个具有所需 idleWaitTime 的调度程序,这可能会使用更好的文档。它还需要一堆你可能想或不想修改的东西,具体取决于你正在做的事情。对于线程池,我只使用 Quartz.net 的默认 SimpleThreadPool,因为此时我不在乎弄乱线程,也不想解释你是如何这样做的,除非这是问题的全部重点。有关工作商店的信息可在此处获得. 我在这里使用 RAMJobStore 是因为它比 AdoJobStore 更简单,但对于这个例子来说并不重要。dbfailureretryinterval 是本示例不关心的另一个值,因此我只是查找了默认设置的值。对于这个例子,它的值应该是最不重要的,因为它没有连接到数据库。对于 idleWaitTime 可能想要做更多的测试来确定什么是好的值,但我选择通过 scaleTimeBy 缩放其默认值 30 秒,因为这是我用来缩放事情进展速度的方法经过。因此,如果我让程序模拟时间以更快的速度流逝,那么这应该可以做到,那么它应该只在更短的时间内保持空闲状态。需要注意的重要一点是,当以这种方式创建调度程序时,它也不会返回,因此需要单独调用以获取我刚刚创建的调度程序。我不知道为什么会这样,我猜如果您要创建多个调度程序并且不一定要使用所有调度程序,那么这种方式会更好。

毕竟,现在你可能仍然会得到一些失火率。虽然它现在空闲的时间单位要小得多(只有几秒钟,因此可能是可接受的余量,具体取决于您的用例),但它仍然存在问题,即它只是检查它是否有即将到来的触发器接下来的几分之一秒。

那么让我们看看向 BatchTimeWindow 添加时间是否有帮助?

let threadpool = Quartz.Simpl.SimpleThreadPool()
let threadexecutor = Quartz.Impl.DefaultThreadExecutor()
let jobstore = Quartz.Simpl.RAMJobStore()
let schedulepluginmap = System.Collections.Generic.Dictionary<String,Quartz.Spi.ISchedulerPlugin>()
let idleWaitTime = TimeSpan.FromSeconds(30.0/timeScale)
let maxBatchSize = 1
let batchTimeWindow = TimeSpan.FromSeconds(timeScale)
let scheduleexporter = Quartz.Simpl.RemotingSchedulerExporter()
Quartz.Impl.DirectSchedulerFactory.Instance.CreateScheduler("TestScheduler","TestInstance",threadpool,threadexecutor,jobstore,schedulepluginmap,idleWaitTime,maxBatchSize,batchTimeWindow,scheduleexporter)
let scheduler = Quartz.Impl.DirectSchedulerFactory.Instance.GetScheduler("TestScheduler")

现在,这有更多的变量在本示例中并不真正关心,甚至不会费心复习,因为调整 batchTimeWindow 实际上会使情况变得更糟。就像让你在 30 分钟前回到失火状态。所以不,batchTimeWindow 虽然看起来可能有用,但不是。只修改 idleWaitTime。

理想情况下,此用途需要较短的等待时间和较大的提前时间,但该选项似乎不可用。

于 2015-04-23T16:21:00.027 回答