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

Use*_*234 22 .net c# wpf timer

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

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

我有一个午夜计时器,应该每个午夜工作以重置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();
        }
Run Code Online (Sandbox Code Playgroud)

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

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

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

doc*_*tan 16

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

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

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

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

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

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

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

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

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

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

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

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


Pet*_*iho 7

那是因为在休眠时它只是将事件处理推迟了与休眠相同的时间吗?

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

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

在某些情况下,可以使用没有此问题的计时器类。这两个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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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