当前位置: 首页 > 知识库问答 >
问题:

计时器回调每24小时触发一次-DST处理是否正确?

慕河
2023-03-14

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

为了每天运行任务,我使用了System.线程。一个周期为24小时的计时器,像这样:

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

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

  • DST是无用的,我们应该把它扔掉
  • 计时器处理得正确吗?我认为不管时钟是否改变,它都不会等待24小时。它只是等待指定的时间段,然后再次调用TimerCallback
  • DST是无用的,我们真的应该摆脱它

我的第二个想法正确吗?如果是,我能做些什么来避免这个问题?如果发生了DST更正,则任务不能在一小时后或更早运行。

共有3个答案

邓威
2023-03-14

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

我现在使用SystemEvents。TimeChanged在系统时间发生变化时获取事件(意外),但是,此事件需要一个类似表单的消息循环。

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

如果有人得到了不需要这样一个消息循环的答案,我会接受的!

卞安邦
2023-03-14

如果有人不需要这样的消息循环就能得到答案

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

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

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

但是当然,在你的例子中,你非常喜欢SystemEvents类的工作方式,你真的需要那个帮助线程,所以你不必自己编写它。请记住,TimeChanged事件在一个完全任意的线程上触发,该线程与您的服务启动的任何线程无关,因此当然需要正确的联锁。顺便说一句,你自己也有同样的问题。

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

子车高超
2023-03-14

SystemEvents。TimeChanged将告诉您时钟是否已被用户更改。它不会随着时钟的每一次滴答声而定时发射。因此,它对安排事件没有用处,只是在事件发生时,您可能会重新计算计时器。(我认为如果系统与时间服务器同步,它也会触发,但我对此并不乐观。)

如果你试图按照汉斯的建议计算墙壁时间的差异,那就要小心了。你不能只使用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

即使指定了本地种类,也不考虑DST。

如果要采用这种方法,必须使用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

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

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

另一个想法是稍微改变一下。与短轮询不同的是,您可以对列表进行排序,并设置到下一个事件的延迟时间,以及一些最大延迟(可能是一个小时)。同样,当计时器启动时,您会看到是否有什么事情要做,或者是否需要设置另一个计时器延迟。您必须在应用程序启动时以及计划新事件的任何时候运行此程序。

使用这两种方法中的任何一种,您都应该使用日期时间偏移量,或等效的UTC日期时间。否则,您可能会在错误的DST时间开火,甚至在DST回退过渡期间开火两次。

如果所有这些听起来都太复杂,那么你可以尝试一个预先构建的解决方案,比如问uartz.net.特别是,阅读他们常见问题的这一部分。

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

 类似资料:
  • 问题内容: 在我的应用程序中,我对用户的得分进行排名。但是,我仅每24小时计算一次我的排名。我的问题是我不知道在不打扰用户的情况下将计算代码放在哪里。 是否不可能在“后台”中每24小时调用一次代码?因为此刻,当第一个用户在24小时之后使用我的应用程序时,将调用计算代码,但是随后用户必须等待几分钟,直到计算结束。我每个用户的数据都保存在Firebase中。 提前致谢! 问题答案: 编辑2019年4月

  • 问题内容: 这是我遇到的问题-我需要每2小时触发一次作业构建,但前提是存在git commit(如果没有活动,则跳过)。我可以单独解决它们,但不确定如何一起解决- 有人有什么好主意吗?我唯一能想到的是一个cron作业,该作业每2小时检查一次,并且在此期间是否有git commit,请手动触发作业,但这看起来并不那么优雅。 任何好的想法表示赞赏。 问题答案: 将詹金斯设置为每2小时(0 * / 2

  • 我正在尝试使用Spark结构化流的功能,触发一次,来模拟一个类似的批处理设置。然而,当我运行我的初始批处理时,我遇到了一些麻烦,因为我有很多历史数据,因此我也使用了这个选项。选项(" cloud files . includeexistingfiles "," true ")也处理现有文件。 因此,我的初始批处理变得非常大,因为我无法控制批处理的文件量。 我也尝试过使用选项 cloudFiles.

  • 我们有一个quartz应用程序可以在其他环境中很好地工作。 触发器表也显示它从未被激发。 NEXT_FIRE_TIME PREV_FIRE_TIME优先级TRIGGER_STATE TRIGGER_TYPE START_TIME END_TIME CALENDAR_NAME MISFIRE_INSTR 1405630189133-1 5等待CRON 1405624813000 0 140 5630

  • 我正在做一个日历应用程序。在此中,来自不同时区的用户创建/查看事件。我想按照下面的方法将事件时间保存在UTC中,并在用户的本地时间中显示它们。注意:用户有他们喜欢的时区设置。 若要将事件开始时间保存为UTC时间戳: 获取用户时区中的事件开始时间: 上面使用的PHP API是否能处理所有区域的DST? 不同年份的DST时间本身会发生变化,PHP是如何获取这方面的信息的?我们需要升级PHP吗?如果是这

  • 对于Java 8之后的JVM 当metaspace的大小