How System.Timers.Timer behave in WPF application, after Hibernate, and Sleep?

后端 未结 4 790
说谎
说谎 2020-12-29 22:43

I\'m using System.Timers.Timer in my WPF application. I want to understand how Timer does behave, after Computer is hibernated, and sleep. I\'m getting some

4条回答
  •  不思量自难忘°
    2020-12-29 23:15

    Is that because while hibernating it just postpones the event handling by the same amount of time it was hibernated?

    While the computer is in a suspended mode (i.e. sleep or hibernate), it doesn't do anything. This includes, in particular, the scheduler that handles waking up the thread that is monitoring the queue of timer events isn't running and so that thread isn't making any progress towards resuming execution to handle the next timer.

    It's not so much that the event is explicitly postponed per se. But yes, that's the net effect.

    In some cases, it's possible to use a timer class that doesn't have this issue. Both System.Windows.Forms.Timer and System.Windows.Threading.DispatcherTimer are based not on the Windows thread scheduler, but instead on the WM_TIMER message. Because of the way this message works — it is generated "on the fly" when a thread's message loop checks the message queue, based on whether the expiration time for the timer has passed…in a way, it's similar to the polling work-around described in the other answer to your question — it's immune to delays that would otherwise be caused by the computer being suspended.

    You've stated your scenario involves a WPF program, so you may find that your best solution is actually to use the DispatcherTimer class, instead of System.Timers.Timer.

    If you do decide you need a timer implementation that isn't tied to the UI thread, here's a version of System.Threading.Timer that will correctly take into account time spend while suspended:

    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();
        }
    }
    

    The public interface for System.Threading.Timer, and the subset interface above copied from that class, is different from what you'll find on System.Timers.Timer, but it accomplishes the same thing. If you really want a class that works exactly like System.Timers.Timer, it should not be hard to adapt the above technique to suit your needs.

提交回复
热议问题