2009-10-06 10 views
21

Mam usługę windows napisany w C#, który tworzy ciężarówkę wątków i sprawia, że ​​wiele połączeń sieciowych (WMI, SNMP, prosty TCP, http). Podczas próby zatrzymania usługi Windows za pomocą przystawki MSC usług, wywołanie zatrzymania usługi zwraca się względnie szybko, ale proces trwa nadal przez około 30 sekund.Jak prawidłowo zatrzymać wielowątkową usługę Windows .NET?

Podstawowe pytanie brzmi: co może być powodem, dla którego zatrzymanie trwa 30 sekund. Czego mogę szukać i jak go szukać?

Drugim pytaniem jest, dlaczego usługa msc snap-in (kontroler usług) powraca, mimo że proces nadal trwa. Czy istnieje sposób, aby zmusić go do powrotu tylko wtedy, gdy proces zostanie faktycznie zabity?

Oto kod w OnStop metody usługi

protected override void OnStop() 
{ 
    //doing some tracing 
    //...... 

    //doing some minor single threaded cleanup here 
    //...... 

    base.OnStop(); 

    //doing some tracing here 
} 

Edycja w odpowiedzi na gwint porządki odpowiedzi

Wielu z was odpowiedziało, że należy śledzić wszystkich moich wątków i następnie je wyczyść. Nie sądzę, że jest to praktyczne podejście. Po pierwsze, nie mam dostępu do wszystkich zarządzanych wątków w jednej lokalizacji. Oprogramowanie jest dość duże z różnymi komponentami, projektami, a nawet bibliotekami zewnętrznymi, które mogą tworzyć wątki. Nie ma sposobu, aby śledzić wszystkie z nich w jednym miejscu lub mieć flagę sprawdzaną przez wszystkie wątki (nawet jeśli mógłbym mieć wszystkie wątki sprawdzające flagę, wiele wątków blokuje takie rzeczy jak semafory.) Gdy blokują, mogą Sprawdź, będę musiał zmusić ich do czekania z limitem czasu, następnie sprawdź tę flagę globalną i poczekaj jeszcze raz).

Flaga IsBackround jest interesującą rzeczą do sprawdzenia. Znowu jednak, jak mogę się dowiedzieć, czy mam jakieś wątki runiczne naokoło? Będę musiał sprawdzić każdą sekcję kodu, który tworzy wątek. Czy jest jakiś inny sposób, może narzędzie, które może mi pomóc znaleźć to.

Jednak proces się kończy. Wydawałoby się tylko, że muszę na coś poczekać. Jednakże, jeśli czekam w metodzie OnStop przez X ilość czasu, to proces trwa około 30 sekund + X, aby zatrzymać. Niezależnie od tego, co próbuję zrobić, wydaje się, że proces wymaga około 30 sekund (nie zawsze jest to 30 sekund, może się różnić) po powrocie OnStop, aby proces rzeczywiście się zatrzymał.

+0

Czy można umieścić w wszystko, aby zatrzymać inne wątki odpowiednio? Czy są to wątki w tle lub wątki pierwszoplanowe? –

+3

Jeśli używasz komponentów, które mogą wewnętrznie tworzyć wątki, najlepiej byłoby, gdyby każdy z nich miał odpowiedni mechanizm zamykania, który można wywołać w OnStop, więc nie musisz bezpośrednio zarządzać ich wątkami. Jeśli nie, lub jeśli nie chcesz zajmować się czystym wyjściem i chcesz natychmiast zakończyć proces, spróbuj wywołać Environment.Exit ... jednak nie jestem pewien, jak SCM zareaguje, gdy usługa zostanie zakończona wysyła polecenie zatrzymania. – DSO

Odpowiedz

15

Połączenie w celu zatrzymania usługi zwraca po otrzymaniu zwrotnego połączenia telefonicznego OnStop(). Na podstawie tego, co pokazałeś, twoja metoda OnStop() nie robi wiele, co wyjaśnia, dlaczego tak szybko wraca.

Istnieje kilka sposobów, aby spowodować wyjście z usługi.

Po pierwsze, można przerobić metodę OnStop(), aby zasygnalizować wszystkie wątki do zamknięcia i poczekać, aż się zamkną przed wyjściem. Jak zasugerował @ DS, możesz użyć flagi globalnej bool, aby to zrobić (pamiętaj, aby oznaczyć ją jako volatile). Generalnie używam ManualResetEvent, ale albo zadziała. Skieruj wątki, aby wyjść. Następnie dołącz do wątków z pewnego rodzaju limitem czasu (zwykle używam 3000 milisekund). Jeśli wątki nadal nie zostały zakończone, możesz wywołać metodę Abort(), aby je zamknąć. Ogólnie rzecz biorąc, metoda Abort() jest niezadowolona, ​​ale biorąc pod uwagę, że twój proces i tak wychodzi, nie jest to wielka sprawa. Jeśli konsekwentnie masz wątek, który musi zostać przerwany, możesz przerobić ten wątek, by lepiej reagował na sygnał wyłączenia.

Po drugie, zaznacz swoje wątki jako gwinty background (więcej szczegółów w artykule here). Wygląda na to, że używasz klasy System.Threading.Thread dla wątków, które domyślnie są wątkami pierwszoplanowymi. Dzięki temu wątki nie spowodują zakończenia procesu. To zadziała dobrze, jeśli wykonujesz tylko zarządzany kod.Jeśli masz wątek oczekujący na kod niezarządzany, nie jestem pewien, czy ustawienie właściwości IsBackground spowoduje, że wątek zostanie automatycznie zamknięty po zamknięciu, to znaczy, że nadal możesz przerobić swój model wątków, aby ten wątek odpowiedział żądanie zamknięcia.

+0

Przyjąłem tę odpowiedź, ponieważ wspomniano o właściwości wątku IsBackground. To była jedyna rzecz, którą potrzebowałem zmienić. Nie wierzę w tworzenie globalnej flagi, której powinien używać każdy składnik - to moim zdaniem zbyt wiele sprzężeń. Jednak jeśli wątki są poprawnie oznaczone jako wątki w tle, usługa przestaje działać poprawnie. – Mark

+1

Nie użyłbym flagi/zdarzenia globalnego. To, co zrobiłem, to tworzenie wrappera wokół obiektu System.Threading.Thread. Konstruktor tego opakowania tworzy wątek, ustawia nazwę i ustawia właściwość IsBackground. Mam publiczne metody uruchamiania i zatrzymywania wątku. Metoda Stop() w szczególności ustawia prywatny komunikat ManualResetEvent, który sygnalizuje, że wątek przestał działać. Aby uczynić go w pełni elastycznym, konstruktor akceptuje to, co sprowadza się do delegata System.Threading.ThreadStart, pozwalając każdemu na korzystanie z tej klasy bez konieczności dziedziczenia z niej. –

10

Menedżer sterowania usługą (SCM) zwróci po powrocie z OnStop. Musisz więc naprawić swoją implementację OnStop, aby zablokować, aż wszystkie wątki zostaną zakończone.

Ogólne podejście polega na tym, aby sygnał OnStop sygnalizował zatrzymanie wszystkich wątków, a następnie czekać na ich zatrzymanie. Aby uniknąć blokowania w nieskończoność, możesz nadać wątkom limit czasu na zatrzymanie, a następnie przerwać je, jeśli trwają zbyt długo.

Oto co zrobiłem w przeszłości:

  1. Tworzenie globalną flagę bool nazwie stop, ustawiona na false, gdy usługa jest uruchomiona.
  2. Po wywołaniu metody OnStop ustaw znacznik Stop na wartość true, a następnie wykonaj Thread.Join na wszystkich zaległych wątkach roboczych.
  3. Każdy wątek roboczy jest odpowiedzialny za sprawdzenie flagi Stop i zakończenie, gdy jest prawdziwa. Sprawdzanie to powinno być wykonywane często i zawsze przed długim działaniem, aby uniknąć zbyt długiego wyłączania usługi.
  4. W metodzie OnStop należy również ustawić limit czasu dla połączeń przychodzących, aby dać wątkom ograniczony czas na zakończenie operacji ... po czym po prostu przerwij pracę.

Uwaga # 4 powinieneś dać wystarczająco dużo czasu, aby nici mogły wyjść w normalnym przypadku. Przerwanie powinno nastąpić tylko w nietypowym przypadku, gdy wątek jest zawieszony ... w takim przypadku wykonanie przerwania nie jest gorsze, niż gdyby użytkownik lub system zabił proces (ten drugi, jeśli komputer się wyłącza).

+1

+1, nie ma sposobu, aby rozwiązać ten problem, dopóki nie wiesz, co robią wszystkie twoje komponenty i masz jakiś sposób na połączenie (i zakończenie) ich długotrwałych operacji wykonywanych na różnych wątkach.Możesz zablokować semafory, ustawiając limit czasu na swoich operacjach Wait, wykonując czekanie wewnątrz pętli, a sprawdzanie sprawdza flagę wyłączenia jako warunek wyjścia pętli. –

0

Sygnał wyjścia pętli wątków, wyczyść go i wykonaj wątek Połącz-s .. poszukaj, ile czasu potrzeba na pomiar/stoper, w którym występują problemy. Unikaj nieudanego wyłączania z różnych powodów ..

0

Aby odpowiedzieć na pierwsze pytanie (Dlaczego usługa będzie działać przez 30 sekund): jest wiele powodów. Na przykład podczas korzystania z WCF zatrzymanie hosta powoduje, że proces przestaje akceptować przychodzące żądania i czeka na przetworzenie wszystkich bieżących żądań przed zatrzymaniem.

To samo dotyczyłoby innych rodzajów operacji sieciowych: operacje zostałyby zakończone przed zakończeniem. Dlatego większość żądań sieciowych ma wbudowaną wartość limitu czasu, kiedy żądanie mogło "zawiesić się" (serwer przestał działać, problemy z siecią itp.).

Bez dodatkowych informacji na temat tego, co dokładnie robisz, nie ma sposobu, aby powiedzieć konkretnie, dlaczego trwa to 30 sekund, ale prawdopodobnie jest to limit czasu.

Aby odpowiedzieć na drugie pytanie (Dlaczego kontroler serwisu wraca): Nie jestem pewien. Wiem, że klasa ServiceController ma metodę WaitForState, która pozwala czekać aż do osiągnięcia danego stanu. Jest możliwe, że kontroler usług czeka na określony czas (inny czas oczekiwania), a następnie przymusowo kończy pracę aplikacji.

Jest również bardzo prawdopodobne, że została wywołana metoda base.OnStop, a metoda OnStop powróciła, sygnalizując kontrolerowi ServiceController, że proces się zatrzymał, podczas gdy w rzeczywistości istnieją pewne wątki, które nie zostały zatrzymane. jesteś odpowiedzialny za określenie tych wątków.

1

Prostym sposobem na to może wyglądać następująco:
-pierwsze kreta GLOBAL wydarzenie

ManualResetEvent shutdownEvent;

-w serwis zaczynają tworzyć zdarzenia ręcznego zerowania i ustawić go do początkowego stanu unsignaled

shutdownEvent = new ManualResetEvent(false);

-w przystanek usługa zdarzeń

shutdownEvent.Set();

nie zapomnij, aby czekać na koniec nici

 
do 
{ 
//send message for Service Manager to get more time 
//control how long you wait for threads stop 
} 
while (not_all_threads_stopped); 

-każdy wątek musi sprawdzić od czasu do czasu, zdarzenie, aby zatrzymać

if (shutdownEvent.WaitOne(delay, true)) break;
0

dla ludzi, którzy wyglądają, jak ja, dla rozwiązania krótszego czasu zamknięcia, spróbuj ustawić CloseTimeout z twój ServiceHost.

Teraz staram się zrozumieć, dlaczego tak dużo czasu potrzeba, aby przestać bez niego i myślę, że to problem z wątkami. Spojrzałem w Visual Studio, dołączając do usługi i zatrzymując ją: Mam kilka wątków uruchomionych przez moją usługę, które wciąż działają.

Teraz pytanie brzmi: czy to naprawdę te wątki powodują, że moja usługa tak wolno się zatrzymuje? Czy Microsoft nie pomyślał o tym? Czy nie sądzisz, że może to być problem zwalniający port lub coś innego? Ponieważ tracenie czasu na obróbkę wątków jest stratą czasu i nie ma krótszego czasu zamykania.

0

Matt Davis jest całkiem kompletny.
Kilka punktów; Jeśli masz wątek, który działa wiecznie (ponieważ ma on prawie nieskończoną pętlę i cały haczyk), a zadaniem twojej usługi jest uruchomienie tego wątku, prawdopodobnie chcesz, aby był to wątek pierwszoplanowy.

Ponadto, jeśli któreś z twoich zadań wykonuje dłuższą operację, taką jak wywołanie sproc, więc Twój czas oczekiwania na dołączenie musi być nieco dłuższy, możesz poprosić SCM o więcej czasu na zamknięcie. Zobacz: https://msdn.microsoft.com/en-us/library/system.serviceprocess.servicebase.requestadditionaltime(v=vs.110).aspx Może to być przydatne w celu uniknięcia budzącego lęk statusu "oznaczone do usunięcia". Maksymalna wartość jest ustawiona w rejestrze, więc zwykle żądam maksymalnego oczekiwanego czasu, w którym wątek jest zwykle wyłączany (i nigdy nie przekracza 12 sekund). Zobacz: what is the maximum time windows service wait to process stop request and how to request for additional time

Mój kod wygląda mniej więcej tak:

private Thread _worker;  
private readonly CancellationTokenSource _cts = new CancellationTokenSource(); 

protected override void OnStart(string[] args) 
{ 
    _worker = new Thread(() => ProcessBatch(_cts.Token)); 
    _worker.Start();    
} 

protected override void OnStop() 
{    
    RequestAdditionalTime(4000); 
    _cts.Cancel();    
    if(_worker != null && _worker.IsAlive) 
     if(!_worker.Join(3000)) 
      _worker.Abort(); 
} 

private void ProcessBatch(CancellationToken cancelToken) 
{ 
    while (true) 
    { 
     try 
     { 
      if(cancelToken.IsCancellationRequested) 
       return;    
      // Do work 
      if(cancelToken.IsCancellationRequested) 
       return; 
      // Do more work 
      if(cancelToken.IsCancellationRequested) 
       return; 
      // Do even more work 
     } 
     catch(Exception ex) 
     { 
      // Log it 
     } 
    } 
} 
Powiązane problemy