2011-11-22 16 views
19

我在我的WPF應用程序中使用System.Timers.Timer。我想了解Timer在計算機休眠和睡眠後的表現如何。在計算機從休眠狀態恢復後,我的應用程序出現了一些奇怪的問題。System.Timers.Timer如何在WPF應用程序,Hibernate和Sleep之後運行?

我應該如何處理計時器以及計算機處於休眠/睡眠模式時的行爲?

我有一個午夜計時器應該在每個午夜工作以重置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)到來時,計時器工作,按預期重置默認值,然後處理現有計時器。最後它爲第二天創建一個新的午夜計時器。 但是,如果我在當天試圖休眠計算機,則午夜計時器將不起作用,並且不會重置默認值。

這是因爲在冬眠期間,它只是將事件處理推遲了相同的時間長度而使其處於休眠狀態?

+6

你看到什麼問題(只是好奇)? – M4N

+0

@ M4N我編輯了這個問題。 – User1234

回答

14

這取決於你如何使用你的計時器。如果您使用它們來發起一些不經常發生的事件(大於幾分鐘),那麼您可能會看到一些「奇怪」的行爲。既然你沒有指定那種'怪異'的行爲,我會假設你的程序的計時器會比它應該晚。

說明:進入睡眠/休眠狀態的問題是所有程序都暫停。這意味着你的定時器沒有被更新,因此當你睡覺/休眠並回來時,就好像你在那段時間被凍結了一樣,你正在睡覺/休眠。這意味着,如果您的計時器設置爲一小時後關閉,並且您的計算機在15分鐘的時間內進入休眠狀態,則一旦醒來就會再等待45分鐘,無論計算機睡眠的時間長短。

解決方案:一種修復方法是在發生最後一次事件時保留DateTime。然後,定期關閉計時器(每10秒鐘或10分鐘,取決於所需的精度)並檢查上次執行的日期時間。如果現在和上次執行時間之間的差值大於或等於期望的時間間隔,那麼您運行執行。

這將修復它,以便如果在睡眠/休眠期間發生「應該發生」的事件,它將在您從睡眠/休眠返回時開始。

更新:上述解決方案將工作,我會填寫一些細節以幫助您實施它。

  • 而不是創建/處置新定時器的,創建ONE計時器爲使用RECURRING(自動復位屬性被設置爲真)

  • 單個定時器的時間間隔應該NOT根據事件發生的次數來設置。相反,它應該設置爲一個您選擇的值來代表輪詢頻率(檢查「事件」是否應該運行的頻率)。選擇應該是效率和精確性的平衡。如果你需要它真的接近12:01 AM,那麼你將間隔設置爲5-10秒。如果不太重要,它恰好在12:01 AM,可以將間隔增加到1-10分鐘。

  • 您需要保留上次執行發生時的日期時間下次執行應該發生時。我希望'下一次執行應該發生時',以便每當計時器過去時你都沒有執行(LastExecutionTime + EventInterval),你只需比較當前時間和事件發生的時間。

  • 一旦經過計時器和應該發生(大約上午12:01地方)的情況下,你應該更新存儲的日期時間,然後運行你想在上午12:01運行代碼。

睡眠與休眠澄清:睡眠和休眠之間的主要區別是在睡眠中,一切都保存在RAM中,而休眠保存當前狀態到磁盤。休眠的主要優點是RAM不再需要電力,因此耗費更少的能源。這就是爲什麼建議在使用有限能量的筆記本電腦或其他設備處理休眠時使用休眠。

也就是說,程序的執行沒有區別,因爲它們在任何一種情況下都處於暫停狀態。不幸的是,System.Timers.Timer沒有「喚醒」計算機,所以你不能強制你的代碼在〜12:01 AM運行。

我相信還有其他方法可以「喚醒」計算機,但除非你走這條路線,否則最好的辦法是在計時器的下一個「輪詢事件」出現睡眠時運行「事件」 /休眠。

+0

+1狀態建議檢查 – Unsliced

+0

+1日期時差檢查。 – PRR

+0

@docmanhattan非常感謝!我編輯了問題,並添加了更多細節。你認爲在這種情況下你會解決什麼問題? – User1234

1

System.Timers.Timer是一個基於服務器的定時器(經過事件使用Threadpool,比其他定時器更準確)。當您的計算機進入睡眠模式或休眠模式時,程序的所有狀態都存儲在RAM中。您的應用程序狀態也一樣。系統啓動後,應用程序狀態將由操作系統恢復(與定時器一起)。 「做某事」或嘗試檢測這些事件並不是一個好主意。儘管它可能來自Windows服務。將它留給操作系統來完成它的工作。

3

這是因爲休眠時它只是推遲事件處理的時間相同的時間它是冬眠?

當計算機處於暫停模式(即睡眠或休眠)時,它什麼也不做。這尤其包括處理喚醒正在監視定時器事件隊列的線程的調度程序沒有運行,因此線程在恢復執行以處理下一個定時器方面沒有取得任何進展。

事情並非如此明確推遲本身。但是,是的,這是最終結果。

在某些情況下,可以使用沒有此問題的計時器類。 System.Windows.Forms.TimerSystem.Windows.Threading.DispatcherTimer都不是基於Windows線程調度程序,而是基於WM_TIMER消息。由於這條消息的工作方式爲—,當線程的消息循環根據定時器的到期時間是否已經過去,檢查消息隊列時,它會「隨時」生成;在某種程度上,它類似於輪詢解決方法在the other answer to your question —中描述,它可以避免延遲,否則會導致計算機被暫停。

你已經說過你的場景涉及一個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的課程,就不難適應上述技術以適應你的需求。