2008-12-24 126 views
50

我有一個方法應該延遲運行一段指定的時間。比較使用Thread.Sleep和計時器延遲執行

我應該使用

Thread thread = new Thread(() => { 
    Thread.Sleep(millisecond); 
    action(); 
}); 
thread.IsBackground = true; 
thread.Start(); 

或者

Timer timer = new Timer(o => action(), null, millisecond, -1); 

我讀了一些關於articles使用Thread.Sleep是糟糕的設計。但我不明白爲什麼。

但是對於使用Timer,Timer的配置方法。由於執行延遲,我不知道如何處理Timer。你有什麼建議嗎?

或者如果你有延遲執行的替代代碼也很感激。

回答

39

一個區別是System.Threading.Timer在線程池線程上調度回調,而不是每次都創建一個新線程。如果您在應用程序的生命週期中需要這樣的操作不止一次,這將節省創建和銷燬一堆線程的開銷(這是一個非常耗費資源的進程,正如您參考的文章指出的那樣),因爲它會只是重用池中的線程,並且如果您將同時運行多個計時器,這意味着您將有更少的線程同時運行(同時節省大量資源)。

換句話說,計時器將會更有效率。它也可能更準確,因爲只要您指定的時間量(OS可能使其睡眠時間更長),Thread.Sleep只能保證至少等待。當然,Timer仍不會完全準確,但意圖是儘可能接近指定的時間觸發回調,而這不一定是Thread.Sleep的意圖。

至於銷燬定時器,回調可以接受一個參數,所以你可能能夠傳遞Timer本身作爲參數,並在回調中調用Dispose(雖然我沒有試過這個 - 我想這是回調期間定時器可能被鎖定)。

編輯:不,我想你不能這樣做,因爲你必須在指定Timer構造本身的回調參數。

也許這樣? (再次,還沒有真正嘗試過)

class TimerState 
{ 
    public Timer Timer; 
} 

...並啓動定時器:

TimerState state = new TimerState(); 

lock (state) 
{ 
    state.Timer = new Timer((callbackState) => { 
     action(); 
     lock (callbackState) { callbackState.Timer.Dispose(); } 
     }, state, millisecond, -1); 
} 

鎖定應防止計時器回調從設法釋放之前,定時器,定時器字段已被設置。


附錄:正如評論者指出的那樣,如果行動()做了與UI,然後使用一個System.Windows.Forms.Timer可能是一個更好的選擇,因爲它將運行在UI回調線。但是,如果情況並非如此,那麼Thread.Sleep與Threading.Timer就是Threading.Timer。

+4

也值得指出與System.Windows.Forms.Timer的區別,我相信*在UI線程上調用一個函數,這對WinForms應用程序非常重要! – 2008-12-24 17:13:10

+2

對於未來的讀者,「睡眠」並不是保證至少是事件,據記載它可能更少。 – 2013-04-01 14:04:19

+4

出於好奇......記錄在哪裏? – 2013-04-01 15:05:01

13

我覺得Thread.Sleep很好,如果你真的想暫停應用程序一段指定的時間。我認爲人們說這是一個糟糕的設計的原因是因爲在大多數情況下人們實際上並不希望應用程序暫停。

例如,我正在開發一個pop3客戶端,程序員正在使用Thread.Sleep(1000)等待套接字檢索郵件。在這種情況下,最好將一個事件處理程序連接到套接字並在套接字完成後繼續執行程序。

+2

除了在後的兩個例子是應用程序實際上暫停。海報正在單獨的線程上暫停,而不是直接調用Thread.Sleep。直接調用Thread.Sleep,是的,壞主意。 – 2008-12-24 17:23:11

1

,我有與System.Timer唯一的牛肉是,大部分我所看到的時候它用於查詢服務和開發人員長時間的延遲(小時,分)經常忘記啓動事件他們開始之前定時器。這意味着如果我啓動應用程序或服務,則必須等到定時器(小時,分鐘)才真正執行。

當然,這不是計時器的問題,但我認爲它經常使用不當,因爲它太容易被誤用。

16

使用ThreadPool.RegisterWaitForSingleObject代替定時器:

//Wait 5 seconds then print out to console. 
//You can replace AutoResetEvent with a Semaphore or EventWaitHandle if you want to execute the command on those events and/or the timeout 
System.Threading.ThreadPool.RegisterWaitForSingleObject(new AutoResetEvent(false), (state, bTimeout) => Console.WriteLine(state), "This is my state variable", TimeSpan.FromSeconds(5), true); 
0

@miniscalope沒有不使用而不是定時器ThreadPool.RegisterWaitForSingleObject,System.Threading.Timer將排隊等待回調到一個線程池線程中執行時,時間已過,不需要等待句柄,等待單個對象將綁定一個線程池線程,等待該事件發出信號或在線程調用回調之前超時超時。

2

我記得實施類似於Eric的解決方案。 然而,這是一個工作的;)

class OneTimer 
    { 
     // Created by Roy Feintuch 2009 
     // Basically we wrap a timer object in order to send itself as a context in order to dispose it after the cb invocation finished. This solves the problem of timer being GCed because going out of context 
     public static void DoOneTime(ThreadStart cb, TimeSpan dueTime) 
     { 
      var td = new TimerDisposer(); 
      var timer = new Timer(myTdToKill => 
      { 
       try 
       { 
        cb(); 
       } 
       catch (Exception ex) 
       { 
        Trace.WriteLine(string.Format("[DoOneTime] Error occured while invoking delegate. {0}", ex), "[OneTimer]"); 
       } 
       finally 
       { 
        ((TimerDisposer)myTdToKill).InternalTimer.Dispose(); 
       } 
      }, 
         td, dueTime, TimeSpan.FromMilliseconds(-1)); 

      td.InternalTimer = timer; 
     } 
    } 

    class TimerDisposer 
    { 
     public Timer InternalTimer { get; set; } 
    }