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

后端 未结 4 776
说谎
说谎 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.

    0 讨论(0)
  • 2020-12-29 23:17

    This depends on how you are using your timers. If you are using them to initiate some event that occurs infrequently (greater than a couple minutes) then you will probably see some 'weird' behavior. Since you don't specify what that 'weird' behavior is, I'm going to assume that your program's timer goes off later than it should.

    Explanation: The problem with going to sleep/hibernating is that all programs are suspended. This means that your Timers are not being updated and thus when you sleep/hibernate and come back, it is as if you were frozen for that period of time that you were sleeping/hibernating. This means if you have a timer set to go off in an hour and your computer goes to sleep at the 15 minute mark, once it wakes up it will have another 45 minutes to go, regardless of how long the computer was sleeping.

    Solution: One fix would be to keep a DateTime around of the last time the event occurred. Then, have a timer go off periodically (every 10 seconds or 10 minutes, depending on the precision desired) and check the DateTime of the last execution. If the difference between now and the last execution time is greater than or equal to the interval desired, THEN you run execution.

    This will fix it so that if an event 'should have' occurred during sleeping/hibernating, it will start the moment you return from sleeping/hibernating.

    Update: The solution presented above will work and I'll fill in a couple of details to help you implement it.

    • Instead of creating/disposing of new Timers, create ONE timer to use that is RECURRING (the AutoReset property is set to true)

    • The interval of the single timer should NOT be set according to the next time the event should occur. Instead, it should be set to a value you choose that will represent the polling frequency (how often it checks to see if the 'event' should run). The choice should be a balance of efficiency and precision. If you NEED it to run REALLY close to 12:01 AM then you set the interval to around 5-10 seconds. If it is less important that it be at exactly 12:01 AM, you can increase the interval to something like 1-10 minutes.

    • You need to keep around a DateTime of when the last execution occurred OR when the next execution should happen. I would prefer 'when the next execution should happen' so that you aren't doing (LastExecutionTime + EventInterval) each time the timer elapses, you'll just be comparing the current time and the time the event should occur.

    • Once the timer elapses and the event SHOULD occur (somewhere around 12:01 AM), you should update the stored DateTime and then run the code you want run at 12:01 AM.

    Sleep vs. Hibernate Clarification: The main difference between sleep and hibernate is that in sleep, everything is kept in RAM whereas hibernate saves the current state to disk. The main advantage of hibernate is that the RAM no longer needs power and thus expends less energy. This is why it is recommended to use hibernate over sleep when dealing with laptops or other devices using a finite amount of energy.

    That said, there is no difference in the execution of programs as they are being suspended in either case. Unfortunately, the System.Timers.Timer does not 'wake up' a computer and so you can't enforce your code to be run at ~12:01 AM.

    I believe there are OTHER ways to 'wake up' a computer but unless you go that route the best you can do is run your 'event' during the next 'polling event' of your timer after it comes out of sleep/hibernate.

    0 讨论(0)
  • 2020-12-29 23:21

    An simple alarm class accepting an absolute Utc time that will survive a sleep cycle. The alarm can then be adjusted from the alarm callback to go off again. No periodic mode is supported to keep it simple. Based strongly on the answer from @PeterDuniho. Full disclosure: minimally tested.

    using Microsoft.Win32;
    using System;
    using System.Threading;
    
    class AlarmSleepTolerant : IDisposable
    {
        readonly Timer _timer;
        DateTime? _alarmUtcOpt;
    
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="callback"></param>
        /// <param name="alarmUtcOpt"></param>
        /// <param name="stateOpt"></param>
        public AlarmSleepTolerant(TimerCallback callback, DateTime? alarmUtcOpt = null, object stateOpt = null) 
        {
            SystemEvents.PowerModeChanged += _OnPowerModeChanged;
            _timer = new Timer(callback, stateOpt, Timeout.Infinite, Timeout.Infinite);
            SetAlarmTime(alarmUtcOpt);
        }
        
        /// <summary>
        /// Set the current alarm, if alarmUtc is <= UtcNow, then the alarm goes off immediately.
        /// Pass null to disable the alarm.
        /// </summary>
        /// <param name="alarmUtc"></param>
        public void SetAlarmTime(DateTime? alarmUtcOpt = null)
        {
            lock (_timer)
            {
                _alarmUtcOpt = alarmUtcOpt;
    
                if (!alarmUtcOpt.HasValue)
                {
                    _timer.Change(Timeout.Infinite, Timeout.Infinite); // disables the timer
                }
                else
                {
                    TimeSpan dueIn = _alarmUtcOpt.Value - DateTime.UtcNow;
                    _timer.Change(dueIn.Ticks <= 0 ? 0 : (long)dueIn.TotalMilliseconds, Timeout.Infinite);
                }
            }
        }
    
        public void Dispose()
        {
            SystemEvents.PowerModeChanged -= _OnPowerModeChanged;
            _timer.Dispose();
        }
    
        void _OnPowerModeChanged(object sender, PowerModeChangedEventArgs e)
        {
            // Timers are based on intervals rather than absolute times so they 
            // need to be adjusted upon a resume from sleep.
            // 
            // If the alarm callback was missed during sleep, it will be called now.
            //
            if (e.Mode == PowerModes.Resume)
            {
                lock (_timer)
                    SetAlarmTime(_alarmUtcOpt);
            }
        }    
    }
    
    0 讨论(0)
  • 2020-12-29 23:25

    System.Timers.Timer is a server based timer(elapsed event uses Threadpool. More accurate than other timers). When your computer goes in sleep mode or in hibernate mode, all the state of your program are stored in RAM. Same goes for your application state. Once your system is up your application state will be restored (along with the timer) by the OS. It wont be a good idea to "do something" or try to detect this events. Its possible from a Windows service though. Leave it to the OS to do its job.

    0 讨论(0)
提交回复
热议问题