【问题标题】:Timer callback raised every 24 hours - is DST handled correctly?每 24 小时触发一次计时器回调 - DST 处理是否正确?
【发布时间】:2013-10-03 11:34:44
【问题描述】:

我只是想我解决了一个每 24 小时运行一次任务的服务的方式,以及 DST 可能会如何伤害它。

为了每天运行任务,我使用了一个周期为 24 小时的 System.Threading.Timer,如下所示:

_timer = new System.Threading.Timer(TimerCallback, null,
    requiredTime - DateTime.Now, new TimeSpan(24, 0, 0));

突然想到夏令时校正我有三个想法:

  • DST 没用,我们应该摆脱它。
  • Timer 是否正确处理此问题?我认为不会——它只是等待 24 小时——不管时钟是否改变。它只是等待指定的时间段,然后再次调用 TimerCallback。
  • DST 没用,我们真的应该摆脱它。

我的第二个想法正确吗?如果是,我该怎么做才能避免这个问题?如果发生 DST 更正,则该任务不得晚于或早于一小时运行。

【问题讨论】:

  • 这可能会解决您的问题:stackoverflow.com/questions/3243348/…
  • 无论如何我都不会相信这么长时间的计时器。我宁愿存储我希望它下次运行的日期/时间,然后使用短间隔计时器(可能 10 秒)检查是否已达到该日期/时间,然后运行代码
  • @musefan:我们以前有一个旧服务这样做,但我不被允许这样做,因为它被奇怪地视为“经常检查资源的浪费”。

标签: c# service timer dst


【解决方案1】:

SystemEvents.TimeChanged 会告诉您时钟是否已被用户更改。它不会在时钟的每个滴答声中定期触发。所以它对调度事件没有用,除了你可能会在它发生时重新计算计时器。 (我认为如果系统与时间服务器同步,它也会触发,但我对此并不乐观。)

如果您尝试按照 Hans 的建议计算挂墙时间的差异,请小心。你不能只使用DateTime。观察:

// With time zone set for US Pacific time, there should only be 23 hours
// between these two points
DateTime a = new DateTime(2013, 03, 10, 0, 0, 0, DateTimeKind.Local);
DateTime b = new DateTime(2013, 03, 11, 0, 0, 0, DateTimeKind.Local);
TimeSpan t = b - a;
Debug.WriteLine(t.TotalHours);  // 24

即使指定了本地种类,也不会考虑夏令时。

如果要采用这种方法,则必须使用DateTimeOffset 类型。

DateTimeOffset a = new DateTimeOffset(2013, 03, 10, 0, 0, 0, TimeSpan.FromHours(-8));
DateTimeOffset b = new DateTimeOffset(2013, 03, 11, 0, 0, 0, TimeSpan.FromHours(-7));
TimeSpan t = b - a;
Debug.WriteLine(t.TotalHours);  // 23

您可能需要通过以下方式收集您的输入:

DateTime today = DateTime.Today;      // today at midnight
DateTime tomorrow = today.AddDays(1); // tomorrow at midnight

TimeZoneInfo tz = TimeZoneInfo.Local;
DateTimeOffset a = new DateTimeOffset(today, tz.GetUtcOffset(today));
DateTimeOffset b = new DateTimeOffset(tomorrow, tz.GetUtcOffset(tomorrow));

TimeSpan t = b - a;
Debug.WriteLine(t.TotalHours);  // 23, 24, or 25 depending on DST

但是 - 设置一个计时器运行这么长时间可能不是一个好主意。不仅时钟可能由用户更改或系统时间同步,而且应用程序或系统可能会关闭或重新启动。此外,如果您有很多此类任务,最终可能会因为闲置而消耗大量资源。

一个想法是保留下一次触发事件的列表。在您的应用程序中,您将触发一个短暂的轮询计时器(例如每分钟一次左右),并将当前时间与列表中的值进行比较,以了解您是否真的需要做任何事情。

另一个想法是稍微改变一下。代替短时间的轮询,您将保持您的列表排序,并设置一个延迟时间,直到下一个事件,并具有一些最大延迟(可能是一个小时)。同样,当计时器触发时,您会查看是否有任何事情要做,或者是否需要设置另一个计时器延迟。您必须在您的应用程序启动时运行它,并且任何时候安排新事件。

使用这两种方法中的任何一种,您都应该使用DateTimeOffset 或等效的UTC DateTime 进行调度。否则,您可能会在 DST 的错误时间触发 - 甚至在 DST 回退过渡期间触发两次

如果所有这些听起来都太复杂,那么您可以尝试使用预构建的解决方案,例如 Quartz.net。特别是阅读this section of their FAQ

关于您的第一个和第三个要点,我完全同意 - 但它永远不会发生。即使它确实发生了,我们仍然必须考虑它确实发生的所有多年历史。如果你还没有看过this video,你应该看看。

【讨论】:

  • 感谢您的解释和观察!实际上,我之前计划使用 TimeChanged 事件来重新计算我的 Timer 在引发事件之前必须等待的毫秒数,而不是每秒检查时间。
【解决方案2】:

如果有人在不需要这样的消息循环的情况下得到答案

如果您自己不提供,SystemEvents 类已经提供了自己的隐藏窗口和调度程序循环。它知道你有一个可能看起来很神奇,但它遵循 Windows 编程中一个完善的合同。它在用于添加事件处理程序的线程上使用 Thread.GetApartmentState()。它返回 STA,就像它在任何 GUI 应用程序中一样,然后它相信您的程序实现了 STA 合同并泵送消息循环并且 SystemEvents 没有做任何特别的事情。

如果它返回 MTA,就像它在控制台模式应用程序或服务中所做的那样,那么它假定您的程序没有调度程序循环并支持 MTA 承诺的线程并启动一个新线程。您可以在调试器的 Debug + Windows + Threads 窗口中看到该线程,线程的名称是“.NET SystemEvents”。该线程创建一个隐藏窗口并泵出一个循环,相当于 Application.Run()。你也可以用 Spy++ 看到那个窗口,它的名字是“.NET-BroadcastEventWindow.xxxx”。

值得注意的是,这种行为是导致许多 GUI 程序严重失败的原因,通常发生在 GUI 应用程序中的 UserPreferenceChanged 事件中,并且通常发生在用户解锁工作站时。当程序在不是 STA 的工作线程上创建启动画面时,就会发生这种情况。 SystemEvents 假定程序需要帮助并创建该帮助线程。现在它会在错误的线程上触发事件,该线程是程序用来更新其 UI 的。如果它是一个 STA 线程但允许该线程退出,它也会出错,SystemEvents 尝试在不再存在的线程上触发事件,如果失败则回退到 TP 线程。这在 GUI 应用程序中非常非常糟糕,死锁是常见的结果。

但当然,在您的情况下,您非常喜欢 SystemEvents 类的工作方式,您确实需要该辅助线程,因此您不必自己编写它。请记住,TimeChanged 事件在与您的服务启动的任何线程无关的完全任意线程上触发,因此肯定需要正确的互锁。顺便说一句,您也遇到了同样的问题。

请考虑简单的解决方案。您只需要根据明天的挂钟时间和今天的时间之间的差异来计算计时器间隔。换句话说,绝对时间,而不是增量。如果跨越 DST 更改,这将产生 23 或 25 小时。

【讨论】:

  • 感谢您的精彩解释!我会按照上一段所说的去做。
【解决方案3】:

我找到了我的解决方案,尽管它可能不是 Windows 服务的最佳解决方案。

我现在使用SystemEvents.TimeChanged 在系统时间已更改时获取事件(惊喜),但是,此事件需要像表单一样的消息循环。

这个问题的答案帮助我在 Windows 服务中实现了消息循环: https://stackoverflow.com/a/9807963/777985

如果有人在不需要这样的消息循环的情况下得到答案,我会接受!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-09-15
    • 2011-08-28
    • 1970-01-01
    • 2021-01-09
    • 2021-07-26
    • 2023-04-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多