Mam usługę Windows, która co 5 sekund sprawdza pracę. Używa ona System.Threading.Timer
do obsługi sprawdzania i przetwarzania i Monitor.TryEnter
, aby upewnić się, że tylko jeden wątek sprawdza działanie.Monitor.TryEnter i Threading.Timer wyścigu
Załóżmy, że tak musi być, ponieważ poniższy kod jest częścią 8 innych pracowników utworzonych przez usługę, a każdy pracownik ma swój specyficzny rodzaj pracy, który musi sprawdzić.
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
}
Teraz tutaj jest problem:
Powyższy kod jest umożliwienie 2 wątki zegara, aby dostać się metodą CheckForWork()
. Szczerze mówiąc nie rozumiem, jak to jest możliwe, ale doświadczyłem tego z wieloma klientami, na których działa to oprogramowanie.
Dzienniki dostałem dzisiaj, kiedy pchnął pewne prace wykazały, że sprawdzone do pracy dwa razy i miałem 2 wątki niezależnie próby przetworzenia, które przechowywane powodując praca na niepowodzenie.
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
Dzienniki pisane są asynchronicznie i są w kolejce, więc nie kopać zbyt głęboko na fakt, że czasy pasują dokładnie, chciałem podkreślić to, co widziałem w dziennikach, aby pokazać, że miałem 2 wątki trafiają w sekcję kodu, która, jak sądzę, powinna nigdy nie była dozwolona. (Log i czasy są prawdziwe, tylko odkażone wiadomości)
Ostatecznie, 2 wątki rozpoczynają pobieranie wystarczająco dużego pliku, w którym jeden z nich uzyskuje odmowę dostępu do pliku i powoduje awarię całej aktualizacji.
W jaki sposób powyższy kod rzeczywiście pozwala na to? Doświadczyłem tego problemu w zeszłym roku, kiedy miałem lock
zamiast Monitor
i założyłem, że to tylko dlatego, że Timer ostatecznie zaczął być odpowiednio przesunięty ze względu na blokowanie lock
, że otrzymywałem wątki z timerem, np. Jeden zablokowany na 5 sekund i poszedł przez prawe, gdy Timer wyzwalał kolejne wywołanie zwrotne i obaj w jakiś sposób je wprowadzili. Dlatego wybrałem opcję Monitor.TryEnter
, więc nie będę po prostu stawiać nici z timerem.
Jakaś wskazówka? We wszystkich przypadkach, w których próbowałem rozwiązać ten problem wcześniej, System.Threading.Timer
był jedynym stałym i myślę, że jest to jego podstawowa przyczyna, ale nie rozumiem dlaczego.
Po prostu ciekawy, czy 'Details.StillGettingWork' (lub jego pole zaplecza) oznaczono' volatile'? – itsme86
@ itsme86 'Szczegóły' jest klasą instancji, a' StillGettingWork' jest autorską własnością. Nic nie jest oznaczone jako niestabilne. – TyCobb
Czy nie jest coś takiego, dlaczego stworzono muteksy? https://msdn.microsoft.com/en-us/library/windows/hardware/ff548097(v=vs.85).aspx –