2016-09-15 28 views
6

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.

+0

Po prostu ciekawy, czy 'Details.StillGettingWork' (lub jego pole zaplecza) oznaczono' volatile'? – itsme86

+0

@ itsme86 'Szczegóły' jest klasą instancji, a' StillGettingWork' jest autorską własnością. Nic nie jest oznaczone jako niestabilne. – TyCobb

+0

Czy nie jest coś takiego, dlaczego stworzono muteksy? https://msdn.microsoft.com/en-us/library/windows/hardware/ff548097(v=vs.85).aspx –

Odpowiedz

0

TL; DR
Produkcyjna procedura przechowywana nie była aktualizowana od lat. Pracownicy otrzymywali pracę, której nie powinni byli uzyskać, więc wielu pracowników przetwarzało żądania aktualizacji.


Udało mi się w końcu znaleźć czas, aby prawidłowo ustawić się lokalnie, aby działać jako klient produkcji przez Visual Studio. Chociaż nie udało mi się odtworzyć go tak, jak tego doświadczyłem, przypadkowo natknąłem się na ten problem.

Ci, którzy przyjęli założenia, że ​​wielu pracowników odebrało pracę, były rzeczywiście poprawne i to jest coś, co nigdy nie powinno było się zdarzyć, ponieważ każdy pracownik jest wyjątkowy w swojej pracy i żąda.

Okazuje się, że w naszym środowisku produkcyjnym procedura składowana w celu pobrania pracy w oparciu o rodzaj pracy nie została zaktualizowana w latach (tak, lat!) W przypadku wdrożeń. Wszystko, co sprawdziło się w pracy automatycznie otrzymało aktualizacje, co oznaczało, że jednocześnie sprawdzano pracownika Aktualizacji i pracownika Foo, oboje kończyli z tą samą pracą.

Na szczęście poprawka to strona bazy danych, a nie aktualizacja klienta.

0

widzę w log masz pod warunkiem, że dostał AppDomain restart tam, czy to prawda? Jeśli tak, czy jesteś pewien, że masz jedyny jeden obiekt dla swojej usługi podczas restartu ? Myślę, że w tym czasie nie wszystkie wątki są zatrzymywane dokładnie w tym samym czasie, a niektóre z nich mogą kontynuować odpytywanie kolejki pracy, więc dwa różne wątki w różnych AppDomain s mają ten sam Id do pracy.

Prawdopodobnie mógł rozwiązać ten z oznaczeniem swoją _workCheckLocker z static słowo kluczowe, tak:

static object _workCheckLocker; 

i wprowadzić statyczny konstruktora dla klasy z inicjalizacji tej dziedzinie (w przypadku inicjalizacji inline można zmierzyć trochę bardziej skomplikowane problemy), ale nie jestem pewien, czy to wystarczy dla twojego przypadku - podczas aktualizacji klasy statycznej zostanie ponownie załadowany.Jak rozumiem, nie jest to opcja dla ciebie.

Być może zamiast przedmiotu dla pracowników możesz wprowadzić słownik static, aby móc sprawdzać, czy w dokumentach znajduje się dokument Id.

Innym podejściem jest obsłużyć zdarzenia Stopping za usługi, które prawdopodobnie można by nazwać podczas AppDomain restart, w której będziesz wprowadzenia CancellationToken i użyć go, aby zatrzymać wszystkie prace w takich okolicznościach.

Ponadto, jak powiedział @ fernando.reyes, można wprowadzić ciężką strukturę blokad zwaną mutex dla synchronizacji, ale to obniży wydajność.

+0

AppDomain służy do załadowania klas, które wykonują rzeczywiste przetwarzanie potrzebne robotnikowi. Pracownik jest ogólny. Kiedy dostaje aktualizację, zasadniczo się aktualizuje. Wielkie dzięki za twój czas. Po prostu znajdę dzień, w którym uda mi się go pobić i spróbować go odtworzyć za pomocą Visual Studio. – TyCobb

+0

Oh, ok. Myślę, że podany kod jest bezpieczny dla wątków. Może z jakiegoś powodu dwóch różnych pracowników ma ten sam plik do przetworzenia. – VMAtm

+0

Zrobili .... = / – TyCobb