2013-12-22 120 views
0

我想以指定的時間間隔調用任務。並且避免調用新的任務,除非最後的任務已經完成。只需要第一個任務,忽略其餘的任務

private async void OnTimerTick(object sender, object e) 
{    
    if (_criticalSection.IsEntered()) return; // only allow 1 at any given time, ignore the rest 
    using (var section = await _criticalSection.EnterAsync()) 
    { 
     await update(); 
    } 
} 

我該如何做到這一點?任何建議更好的模式?

+0

你的代碼不是線程安全的,它是一個種族。 –

+0

@SriramSakthivel:你的意思是在繼續使用block和IsEntered的版本之間存在競爭?但是如果SynchronizationContext是相同的,怎麼會有一場比賽呢? – JonPall

回答

1

臨界區(如Window的互斥體)用於互斥:只允許單個線程進入代碼路徑。

但是,這不是你想要做的事情:你需要的東西會告訴你是否有事情發生。

一個更好的方法將是一個手動復位事件:設置(也稱爲信號)在任務開始和結束時復位。然後,您可以通過等待正常Window事件的超時值爲零,或使用適用於其他類型事件的成員來檢查它是否通過信號發送。

由於這似乎都在一個單一的過程中,一個好的起點是System.Threading.ManualRestEventSlim。使用類似於:

// One off initialisation somewhere at class scope 
private static ManualResetEventSlim taskRunning = new ManualResetEventSlim(); 
private static object taskLock = new Object(); 

// code called from the timer, do in a lock to avoid race conditions with two 
// or more threads call this. 
lock (taskLock) { 
    if (!taskRunning.IsSet) { 
    StartTheTask(); // assuming this does not return until task is running. 
    } 
} 

// At the outermost scope of the code in the task: 
try { 
    Debug.Assert(!taskRunning.IsSet); // Paranoia is good when doing threading 
    taskRunning.Set(); 

    // Task impementation 

} finally { 
    Debug.Assert(taskRunning.IsSet); // Paranoia is good when doing threading 
    taskRunning.Reset(); 
} 

另一種方法是始終啓動任務,但讓它檢查事件,如果設置則立即退出。這仍然需要lock來避免在線程間調用IsSetSet()之間的比賽。第二種方法將檢查代碼保存在一起,代價是簡單地運行另一個任務(除非這很常見,否則我可能會將此方法用於代碼局部性)。

+0

尼斯和快速的回​​應,謝謝。但是Win32的TryEnterCriticalSection()和SemaphoreSlim的超時參數背後的原因是什麼?它們在邏輯上不適用嗎? – JonPall

+0

@ user3127135正如答案中的關鍵部分(如muxtexes)中所述,是同類對象的錯誤種類。可以使用信號量,但不需要它提供的計數:它會更復雜。 – Richard

+0

使用ManualResetEventSlim和線程安全的布爾標誌有什麼區別嗎? – JonPall

相關問題