我有一个 Windows 服务处理由 System.Timers.Timer 触发的事件。 我想将该计时器的间隔设置为 3 个月。
System.Timers.Timer 的 Interval 属性是一个以毫秒为单位的 Int32,并且 Int32.MaxValue 小于 3 个月(以毫秒为单位)。
我该怎么办?
你会重新思考你的设计。存储您下次要执行事件的时间(例如注册表、文件、数据库...),然后定期唤醒以检查该时间是否已过。即使您可以将 System.Timers.Timer 设置为 3 个月,系统也可能会在计时器关闭之前重新启动,并且您会丢失事件。
另一种选择是使用由 Windows Scheduler 执行的计划作业。它将运行一个小程序,向您的服务发送一条消息,表明事件已发生。与定期醒来检查 3 个月是否已经过去相比,这会占用更少的资源,但更复杂。
有同样的问题,只不过它是一个通用系统,可以以完全可调的时间间隔(从毫秒到几个月)做同样的事情。嗯,是“应该”的。事实证明,由于这个原因,它确实在大于 24.8 天的间隔上出现了混乱。 就我而言,“重新考虑方法”是不可能的,因为这只是一个更大的 Windows 服务系统的一个小问题子案例。
解决方案相当简单,但请注意,我有一个辅助系统,它为我提供了下一次执行:
DateTime
;计时器的工作只是匹配实际执行任务,因此它只需从中减去
DateTime.Now
即可计算出时间间隔。在这种情况下,我需要做的就是在计时器对象旁边保留一个溢出布尔值,并在检查该间隔时进行设置 Int32.MaxValue
:
private Timer _timer;
private Boolean _overflow;
// Amount of days between executions.
private Int64 _intervalDays;
// Offset time inside day. Is prepared in advance to be lower than a full day.
private Int32 _offsetInDay;
// Last time the operation was executed.
private DateTime _lastTime;
private void QueueNextTime(DateTime thisTime)
{
TimeSpan interval = this.GetNextRunTime(thisTime) - DateTime.Now;
// Not using TotalMilliseconds since it adds an extra conversion to Double.
Int64 intervalMilliSeconds = interval.Ticks / 10000;
if (intervalMilliSeconds <= 0)
intervalMilliSeconds = 1;
// If interval greater than Int32.MaxValue, set the boolean to skip the next
// run. The interval will be topped at Int32.MaxValue. The TimerElapsed
// function will call this function again anyway, so no need to store any
// information on how much is left. It'll just repeat until the overflow
// status is 'false'.
bool wasOverflow = this._overflow;
this._overflow = intervalMilliSeconds > Int32.MaxValue;
this._timer.Interval = Math.Min(intervalMilliSeconds, Int32.MaxValue);
if (!wasOverflow)
{
// Execution actually occurred. Save time.
this._lastTime = thisTime;
}
this._timer.Start();
}
// The actual system I used was more advanced in choices of interval and
// offset types (could do stuff like "12th of each month"), but this is
// the basic gist of it, simplified to a system that's basically
// "every x days, at HH:mm:ss"
private DateTime NextRunTime(DateTime lastRunTime)
{
// Take start of execution day as basis.
DateTime next = new DateTime(_lastTime.Year, _lastTime.Month, _lastTime.Day);
// start of day after current run day.
DateTime afterLastRunDay =
new DateTime(lastRunTime.Year, lastRunTime.Month, lastRunTime.Day)
.AddDays(1);
// Full system can load "_lastTime" from settings to resume after a cold
// start. The loop ensures the schedule is retained as if it never stopped.
while (next <= afterLastRunDay)
{
next = next.AddDays(_intervalDays);
}
return next.AddMilliseconds(_offsetInDay);
}
// The function linked to _timer.Elapsed
private void TimerElapsed(object sender, ElapsedEventArgs e)
{
this._timer.Stop();
if (this._overflow)
{
QueueNextTime(e.SignalTime);
return;
}
// Execute tasks
// ...
// ...
// schedule next execution, based on when this execution run started.
QueueNextTime(e.SignalTime);
}
当然这是简化的;真实的系统有 try/catch 和一个根据外部停止命令中止的系统。但这就是要点。
您可以轻松地将相关作业状态存储到数据库中(以防万一机器死掉,它会死掉) - http://quartznet.sourceforge.net/tutorial/lesson_9.html,还有更多用处。 我已经在多个生产系统上使用了 Quartz.net,据我所知,这些流程至今仍在运行:)
为什么不使用 System.Timer -
http://quartznet.sourceforge.net/faq.html#whynottimer
如果您想以 3 个月的间隔触发事件,则必须保留退出应用程序时计时器所用的总时间的信息。
已更新所以你必须把你的间隔分成几个部分,并在每次经过时设置一个计数器并递增它。每次经过时检查它,直到达到3个月。
例如
int interval = 10 * 24 * 60 * 60 * 1000; // Time interval for 10 days
int counter = 0;
private void timer1_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
counter++;
if (counter == 9)
{
//do your task for 3 months
//since counter increments 9 times at interval of 10 days
//so 9*10=90 days i.e. nearly equal to 3 months
counter = 0;
}
}