在Hibernate和Sleep之后,System.Timers.Timer如何在WPF应用程序中运行?

我在WPF应用程序中使用System.Timers.Timer 。 我想了解Timer在计算机hibernate和睡眠后的行为方式。 计算机从hibernate状态恢复后,我的应用程序出现了一些奇怪的问题。

我应该如何处理计时器,以及它们在计算机处于hibernate/睡眠模式时的行为方式?

我有一个午夜计时器,应该每个午夜工作以重置UI上的默认值。

以下是创建计时器的代码:

private void ResetMidnightTimer() { // kill the old timer DisposeMidnightTimer(); _midnightTimer = new Timer(); // scheduling the timer to elapse 1 minute after midnight _midnightTimer.Interval = (DateTime.Today.AddDays(1).AddMinutes(1) - DateTime.Now).TotalMilliseconds; _midnightTimer.Elapsed += (_, __) => UpdateRecommendedCollectingTime(); _midnightTimer.Enabled = true; _midnightTimer.Start(); } 

在UI页面的构造函数中,我调用调用ResestMidnightTimer()的方法并在事实上创建计时器。 之后,计时器只等了一晚。

当夜间(实际上是12:01 AM)到来时,计时器工作,按预期重置默认值,然后处理现有计时器。 最后它为第二天创建了一个新的午夜计时器。 但是如果我在那天尝试hibernate计算机,午夜计时器将无法工作,也不会重置默认值。

这是因为虽然冬眠它只是将事件处理推迟了相同的时间才被冬眠了吗?

这取决于您使用计时器的方式。 如果您使用它们来启动不经常发生的事件(大于几分钟),那么您可能会看到一些“怪异”的行为。 既然你没有指定那种“怪异”的行为,我会假设你的程序的计时器比它应该的时间晚了。

说明:进入睡眠/hibernate状态的问题是所有程序都被暂停。 这意味着你的计时器没有被更新,因此当你睡觉/hibernate并回来时,就好像你在那段时间被冻结,你正在睡觉/hibernate。 这意味着如果你有一个计时器设置为在一小时内熄灭,你的计算机在15分钟后进入睡眠状态,一旦它被唤醒,它将有另外45分钟的时间,无论计算机睡多久。

解决方案:一个修复方法是在事件发生的最后一次保持DateTime。 然后,定时关闭计时器(每10秒或10分钟,具体取决于所需的精度)并检查上次执行的DateTime。 如果now和上次执行时间之间的差异大于或等于所需的时间间隔,则执行。

这将解决它,以便如果在睡眠/hibernate期间发生’应该’事件,它将从您从睡眠/hibernate状态返回时开始。

更新:上面提到的解决方案将起作用,我将填写一些细节来帮助您实现它。

  • 而不是创建/处理新的计时器,创建一个使用它的计时器RECURRING (AutoReset属性设置为true)

  • 不应根据下次发生事件时设置单个计时器的间隔。 相反,它应该设置为您选择的值,该值将表示轮询频率(它检查“事件”是否应该运行的频率)。 选择应该是效率和精度的平衡。 如果你需要它接近12:01 AM运行,那么你将间隔设置为大约5-10秒。 如果在凌晨12点01分它不那么重要,你可以将间隔增加到1-10分钟。

  • 您需要保留上次执行发生时下次执行发生时的DateTime。 我希望“当下一次执行发生时”,以便每次计时器过去时你都没有(LastExecutionTime + EventInterval),你只需要比较当前时间和事件应该发生的时间。

  • 一旦计时器过去并且应该发生事件(大约在凌晨12:01左右),您应该更新存储的DateTime,然后运行您希望在上午12:01运行的代码。

睡眠与hibernate澄清:睡眠和hibernate之间的主要区别在于,在睡眠中,一切都保存在RAM中,而hibernate则将当前状态保存到磁盘。 hibernate的主要优点是RAM不再需要电源,因此消耗的能量更少。 这就是为什么建议在使用有限量的能量来处理笔记本电脑或其他设备时使用hibernate。

也就是说,程序的执行没有区别,因为它们在任何一种情况下都被暂停。 不幸的是,System.Timers.Timer不会“唤醒”计算机,因此您无法强制执行代码以在凌晨12:01运行。

我相信还有其他方法可以“唤醒”一台计算机,但除非你走这条路,否则你可以做的最好的事情就是在你的计时器出现睡眠/hibernate状态后的下一次“轮询事件”中运行你的’事件’。

这是因为虽然冬眠它只是将事件处理推迟了相同的时间才被冬眠了吗?

当计算机处于挂起模式(即睡眠或hibernate)时,它不会执行任何操作。 这尤其包括处理唤醒正在监视定时器事件队列的线程的调度程序未运行的调度程序,因此该线程没有在恢复执行以处理下一个计时器方面取得任何进展。

并不是说事件本身就被明确推迟了 。 但是,是的,这是净效应。

在某些情况下,可以使用没有此问题的计时器类。 System.Windows.Forms.TimerSystem.Windows.Threading.DispatcherTimer都不基于Windows线程调度程序,而是基于WM_TIMER消息。 由于此消息的工作方式 – 当线程的消息循环检查消息队列时,它是“动态”生成的,基于计时器的到期时间是否已经过去…在某种程度上,它类似于轮询工作在你的问题的另一个答案中描述 – 它不会因计算机被暂停而导致延迟。

您已经声明您的方案涉及WPF程序,因此您可能会发现您的最佳解决方案实际上是使用DispatcherTimer类,而不是System.Timers.Timer

如果您确定需要一个与UI线程无关的计时器实现,那么这是一个版本的System.Threading.Timer ,它将正确地考虑暂停时的花费:

 class SleepAwareTimer : IDisposable { private readonly Timer _timer; private TimeSpan _dueTime; private TimeSpan _period; private DateTime _nextTick; private bool _resuming; public SleepAwareTimer(TimerCallback callback, object state, TimeSpan dueTime, TimeSpan period) { _dueTime = dueTime; _period = period; _nextTick = DateTime.UtcNow + dueTime; SystemEvents.PowerModeChanged += _OnPowerModeChanged; _timer = new System.Threading.Timer(o => { _nextTick = DateTime.UtcNow + _period; if (_resuming) { _timer.Change(_period, _period); _resuming = false; } callback(o); }, state, dueTime, period); } private void _OnPowerModeChanged(object sender, PowerModeChangedEventArgs e) { if (e.Mode == PowerModes.Resume) { TimeSpan dueTime = _nextTick - DateTime.UtcNow; if (dueTime < TimeSpan.Zero) { dueTime = TimeSpan.Zero; } _timer.Change(dueTime, _period); _resuming = true; } } public void Change(TimeSpan dueTime, TimeSpan period) { _dueTime = dueTime; _period = period; _nextTick = DateTime.UtcNow + _dueTime; _resuming = false; _timer.Change(dueTime, period); } public void Dispose() { SystemEvents.PowerModeChanged -= _OnPowerModeChanged; _timer.Dispose(); } } 

System.Threading.Timer的公共接口以及从该类复制的上面的子集接口与您在System.Timers.Timer找到的不同,但它完成了同样的事情。 如果你真的想要一个与System.Timers.Timer完全相同的类,那么应该很难适应上述技术来满足你的需求。

System.Timers.Timer是一个基于服务器的计时器(已用事件使用Threadpool。比其他计时器更准确)。 当您的计算机进入睡眠模式或hibernate模式时,程序的所有状态都存储在RAM中。 您的应用程序状态也是如此。 系统启动后,操作系统将恢复应用程序状态(以及计时器)。 “做某事”或尝试检测此事件不是一个好主意。 它可以从Windows服务中获得。 将它留给操作系统来完成它的工作。