我有一個Windows服務,每5秒檢查一次工作。它使用System.Threading.Timer
來處理檢查和處理,並使用Monitor.TryEnter
來確保只有一個線程正在檢查工作。Monitor.TryEnter和Threading.Timer競爭條件
假設它必須是這種方式,因爲以下代碼是由服務創建的8個其他工作人員的一部分,每個工作人員都有自己需要檢查的特定類型的工作。
readonly object _workCheckLocker = new object();
public Timer PollingTimer { get; private set; }
void InitializeTimer()
{
if (PollingTimer == null)
PollingTimer = new Timer(PollingTimerCallback, null, 0, 5000);
else
PollingTimer.Change(0, 5000);
Details.TimerIsRunning = true;
}
void PollingTimerCallback(object state)
{
if (!Details.StillGettingWork)
{
if (Monitor.TryEnter(_workCheckLocker, 500))
{
try
{
CheckForWork();
}
catch (Exception ex)
{
Log.Error(EnvironmentName + " -- CheckForWork failed. " + ex);
}
finally
{
Monitor.Exit(_workCheckLocker);
Details.StillGettingWork = false;
}
}
}
else
{
Log.Standard("Continuing to get work.");
}
}
void CheckForWork()
{
Details.StillGettingWork = true;
//Hit web server to grab work.
//Log Processing
//Process Work
}
現在,這裏的問題:
上面的代碼允許2個定時器線程進入CheckForWork()
方法。我真的不明白這是如何可能的,但我已經體驗了這個軟件運行多個客戶端。
當我推送一些工作時,我得到的日誌顯示它檢查了兩次工作,並且有2個線程獨立地嘗試處理導致工作失敗的問題。
Processing 0-3978DF84-EB3E-47F4-8E78-E41E3BD0880E.xml for Update Request. - at 09/14 10:15:501255801
Stopping environments for Update request - at 09/14 10:15:501255801
Processing 0-3978DF84-EB3E-47F4-8E78-E41E3BD0880E.xml for Update Request. - at 09/14 10:15:501255801
Unloaded AppDomain - at 09/14 10:15:10:15:501255801
Stopping environments for Update request - at 09/14 10:15:501255801
AppDomain is already unloaded - at 09/14 10:15:501255801
=== Starting Update Process === - at 09/14 10:15:513756009
Downloading File X - at 09/14 10:15:525631183
Downloading File Y - at 09/14 10:15:525631183
=== Starting Update Process === - at 09/14 10:15:525787359
Downloading File X - at 09/14 10:15:525787359
Downloading File Y - at 09/14 10:15:525787359
日誌異步寫入和進行排隊,所以不挖太深的事實,時代嚴絲合縫,我只是想指出,我在日誌中看到證明我有2個線程擊中了我認爲應該從未被允許的一段代碼。 (日誌和時間是真實的,只是消毒消息)
最終會發生什麼是2線程開始下載足夠大的文件,其中一個最終導致文件訪問被拒絕並導致整個更新失敗。
上面的代碼如何實際允許這個?去年我遇到過這個問題,當時我有一個lock
而不是Monitor
,並且認爲這只是因爲Timer最終開始得到足夠的抵消,因爲我得到了定時器線程堆積,即一個阻塞了5秒鐘,通過正確的計時器觸發另一個回調,他們都以某種方式進入。這就是爲什麼我去了Monitor.TryEnter
選項,所以我不會只是保持堆疊計時器線程。
任何線索?在之前我試圖解決這個問題的所有案例中,System.Threading.Timer
一直是我們不斷的,我認爲它的根本原因,但我不明白爲什麼。
只是好奇,是'Details.StillGettingWork'(或其後臺字段)標記爲'volatile'? – itsme86
@ itsme86'Details'是一個實例類,'StillGettingWork'是一個自動屬性。沒有什麼顯着易變的。 – TyCobb
是不是這樣爲什麼mutexes被創造爲什麼? https://msdn.microsoft.com/en-us/library/windows/hardware/ff548097(v=vs.85).aspx –