2009-11-08 61 views
9

我有一個Windows Service應用程序,它使用Threading.TimerTimerCallback以特定間隔進行一些處理。我需要將這個處理代碼一次只鎖定到一個線程。與Threading.Timer一起使用鎖

因此,例如,服務啓動並觸發第一個回調,並啓動一個線程並開始處理。只要處理在下一次回調之前完成,就可以正常工作。所以說例如處理比平常花費的時間稍長一些,並且在另一個線程正在處理時TimerCallback被再次觸發,我需要讓該線程等待,直到另一個線程完成。

這裏是我的代碼示例:

static Timer timer; 
static object locker = new object(); 

public void Start() 
{ 
    var callback = new TimerCallback(DoSomething); 
    timer = new Timer(callback, null, 0, 10000); 
} 

public void DoSomething() 
{ 
     lock(locker) 
     { 
      // my processing code 
     } 
} 

是該這樣做的安全呢?如果隊列變得相當多,會發生什麼?有更好的選擇嗎?

回答

26

如果可以讓事件在它們之間以固定時間間隔激發(與當前以固定時間間隔觸發它們的代碼相對),那麼您可以啓動計時器而無需一段時間,並且每次排隊一個新的回調,例如

static Timer timer; 

public void Start() 
{ 
    var callback = new TimerCallback(DoSomething); 
    timer = new Timer(callback, null, 0, Timeout.Infinite); 
} 

public void DoSomething() 
{ 
     try 
     { 
      // my processing code 
     } 
     finally 
     { 
      timer.Change(10000, Timeout.Infinite); 
     } 
} 

該代碼告訴新創建的計時器立即觸發一次。在處理代碼中,它完成了工作,然後告訴定時器在10秒內再次觸發一次。由於定時器現在不是週期性地觸發,而是通過其回調方法重新啓動,所以回調保證是單線程的,沒有隊列。

如果你想保持一個固定的時間間隔,那麼有點麻煩,因爲如果處理開始的時間比定時器間隔長,你必須決定該做什麼。一種選擇是做你現在正在做的事情,但實際上最終會導致大量排隊的線程和最終的線程池飢餓。另一個選擇是如果已經有一個正在進行中的話,則簡單地放棄該回叫,例如,

static Timer timer; 
static object locker = new object(); 

public void Start() 
{ 
    var callback = new TimerCallback(DoSomething); 
    timer = new Timer(callback, null, 0, 10000); 
} 

public void DoSomething() 
{ 
     if (Monitor.TryEnter(locker)) 
     { 
      try 
      { 
       // my processing code 
      } 
      finally 
      { 
       Monitor.Exit(locker); 
      } 
     } 
} 
+0

觸發定時器的好主意從來沒有這樣想過。第二種情況,這是否意味着線程只是退出並不等待? – James 2009-11-09 00:08:46

+0

是的,在第二種情況下,如果當前在處理代碼中有另一個線程,TryEnter立即返回,因此它有效地跳過該定時器間隔。 – 2009-11-09 08:58:47

+0

這應該做的伎倆,謝謝。 – James 2009-11-09 11:53:53

1

可能發生,如果處理代碼的時間超過10秒,以執行最糟糕的是,你將浪費每有一個名爲(他們將等待在鎖語句)新的回調時間1個線程池線程。如果你把所有的線程池線程HttpWebRequest,ASP.NET,異步委託調用......都會受到影響。

我會做的是安排第一個回調立即。

public void DoSomething() 
{ 
     DateTime start = DateTime.UtcNow; 
     ... 
     TimeSpan elapsed = (DateTime.UtcNow - start); 
     int due_in = (int) (10000 - elapsed.TotalMilliseconds); 
     if (due_in < 0) 
      due_in = 0; 
     timer.Change (due_in, Timeout.Infinite); 
} 

或沿這條線的東西:那麼,如果你真的需要你的 DoSomething的()將每10s調用。