Comment arrêter correctement un multi-threaded.NET service windows?

J'ai un service windows écrit en C # qui crée une charge de threads et fait de nombreuses connexions réseau (WMI, SNMP, TCP simple, http). Lorsque vous tentez d'arrêter le service windows à l'aide du composant logiciel enfichable Services MSC, l'appel pour arrêter le service revient relativement rapidement, mais le processus continue à s'exécuter pendant environ 30 secondes.

La question principale est de savoir quelle pourrait être la raison pour laquelle il faut plus de 30 secondes pour s'arrêter. Que puis-je rechercher et comment puis-je chercher il?

La question secondaire est pourquoi le service MSC snap-in (Service controller) revient-il même si le processus est toujours en cours d'exécution. Existe-t-il un moyen de le faire revenir uniquement lorsque le processus est réellement tué?

Voici le code de la méthode OnStop du service

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

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

   base.OnStop();

   //doing some tracing here
}

Modifier en réponse aux réponses de nettoyage de threads

Beaucoup d'entre vous ont répondu que je devrais garder une trace de tous mes fils et ensuite les nettoyer. Je ne pense pas que c'est une pratique approche. Premièrement, je n'ai pas accès à tous les threads gérés dans un seul endroit. Le logiciel est assez gros avec différents composants, projets et même des DLL tierces qui pourraient tous créer des threads. Il n'y a aucun moyen que je puisse garder une trace de tous dans un endroit ou avoir un drapeau que tous les threads vérifient (même si je pouvais avoir tous les threads vérifier un drapeau, beaucoup de threads bloquent sur des choses comme les sémaphores. Quand ils bloquent, ils ne peuvent pas vérifier. Je vais devoir les faire attendre avec un délai d'attente, puis vérifiez ce drapeau global et l'attente à nouveau).

Le drapeau IsBackround est une chose intéressante à vérifier. Encore une fois, Comment puis-je savoir si j'ai des threads forground en cours d'exécution? Je vais devoir vérifier chaque section du code qui crée un thread. Est-il un autre moyen, peut-être un outil qui peut m'aider à la trouver.

En fin de compte, le processus s'arrête. Il semblerait seulement que je dois attendre quelque chose. Cependant, si j'attends dans la méthode OnStop pour X ammount de temps, puis il faut le processus environ 30 secondes + X pour arrêter. Peu importe ce que j'essaie de faire, il semble que le processus ait besoin d'environ 30 secondes (ce n'est pas toujours 30 secondes, cela peut varier) après le retour de OnStop pour que le processus s'arrête réellement.

23
demandé sur Mark 2009-10-07 01:29:10

7 réponses

L'appel pour arrêter le service revient dès que votre rappel OnStop() revient. Sur la base de ce que vous avez montré, votre méthode OnStop() ne fait pas grand-chose, ce qui explique pourquoi elle revient si vite.

Il y a plusieurs façons de faire sortir votre service.

Tout d'abord, vous pouvez retravailler la méthode OnStop() pour signaler à tous les threads de se fermer et attendre qu'ils se ferment avant de quitter. Comme @DSO l'a suggéré, vous pouvez utiliser un indicateur bool global pour le faire (assurez-vous de le marquer comme volatile). Je l'utilise généralement un ManualResetEvent, mais l'un ou l'autre fonctionnerait. Signalez aux threads de quitter. Ensuite, rejoignez les threads avec une sorte de délai d'attente (j'utilise habituellement 3000 millisecondes). Si les threads ne sont toujours pas sortis, vous pouvez appeler la méthode Abort() pour les quitter. Généralement, la méthode Abort() est mal vue, mais étant donné que votre processus se termine de toute façon, ce n'est pas grave. Si vous avez toujours un thread qui doit être abandonné, vous pouvez retravailler ce thread pour être plus réactif à votre arrêt signal.

Deuxième, la marque de votre fils comme contexte threads (voir ici pour plus de détails). On dirait que vous utilisez le système.Le filetage.Classe Thread pour les threads, qui sont des threads de premier plan par défaut. Cela permettra de s'assurer que les threads ne retardent pas la sortie du processus. Cela fonctionnera correctement si vous exécutez du code géré uniquement. Si vous avez un thread qui attend du code non géré, Je ne suis pas sûr que la définition de la propriété IsBackground toujours provoquer la sortie automatique du thread à l'arrêt, c'est-à-dire que vous pouvez toujours avoir retravaillé votre modèle de threading pour que ce thread réponde à votre demande d'arrêt.

16
répondu Matt Davis 2017-01-30 15:34:57

Le gestionnaire de contrôle de service (SCM) retournera lorsque vous revenez de OnStop. Vous devez donc réparer votre implémentation OnStop pour bloquer jusqu'à ce que tous les threads soient terminés.

L'approche générale consiste à faire en sorte que tous vos threads s'arrêtent, puis à attendre qu'ils s'arrêtent. Pour éviter de bloquer indéfiniment, vous pouvez donner aux threads une limite de temps pour les arrêter, puis les abandonner s'ils prennent trop de temps.

Voici ce que j'ai fait dans le passé:

  1. créer un indicateur bool global appelé Stop, défini sur false lorsque le service est démarré.
  2. lorsque la méthode OnStop est appelée, définissez L'indicateur Stop sur true, puis faites un Thread.Rejoignez sur tous les threads de travail.
  3. chaque thread de travail est responsable de la vérification de L'indicateur D'arrêt et de la sortie proprement quand elle est vraie. Cette vérification doit être effectuée fréquemment, et toujours avant une opération de longue durée, pour éviter qu'elle ne retarde trop longtemps l'arrêt du service.
  4. dans la méthode OnStop, ayez également un délai d'attente sur la jointure appels, pour donner aux threads un temps limité pour quitter proprement... après qui vous vient de l'abandonner.

Note dans #4, vous devriez donner suffisamment de temps pour que vos threads sortent dans le cas normal. L'abandon ne devrait se produire que dans un cas inhabituel où le fil est suspendu... dans ce cas, faire un abandon n'est pas pire que si l'utilisateur ou le système tue le processus (ce dernier si l'ordinateur s'arrête).

10
répondu DSO 2009-10-06 21:57:38

La façon simple de le faire peut ressembler à ceci:
- première Crète un événement mondial

ManualResetEvent shutdownEvent;

- au démarrage du service, créez l'événement de réinitialisation manuelle et définissez-le sur un état initial de non signalé
shutdownEvent = new ManualResetEvent(false);

- à l'événement d'arrêt de service

shutdownEvent.Set();

n'oubliez pas d'attendre la fin des threads
do
{
 //send message for Service Manager to get more time
 //control how long you wait for threads stop
}
while ( not_all_threads_stopped );

- chaque thread doit tester de temps en temps, l'événement à arrêter

if ( shutdownEvent.WaitOne(delay, true) ) break;
1
répondu lsalamon 2009-10-07 15:59:53

Signalez la sortie de la boucle de vos threads, nettoyez-la et faites thread Join-S.. recherchez combien de temps cela prend comme mesure/chronomètre où les problèmes sont. Évitez l'arrêt avorté pour diverses raisons..

0
répondu rama-jka toti 2009-10-06 21:54:01

Pour répondre à la première question (Pourquoi le service continuerait-il à fonctionner pendant plus de 30 secondes): il existe de nombreuses raisons. Par exemple, lors de l'utilisation de WCF, l'arrêt d'un hôte provoque l'arrêt du processus d'accepter les demandes entrantes, et il attend de traiter toutes les demandes en cours avant de s'arrêter.

La même chose serait vraie pour les autres types d'opérations réseau: les opérations tenteraient de se terminer avant de se terminer. C'est pourquoi la plupart des requêtes réseau ont une valeur de délai d'attente intégrée pour quand la requête peut avoir "accroché" (serveur tombé en panne, problèmes réseau, etc.).

Sans plus d'informations sur ce que vous faites exactement, il n'y a pas moyen de vous dire spécifiquement pourquoi cela prend 30 secondes, mais c'est probablement un délai d'attente.

Pour répondre à la deuxième question (Pourquoi le contrôleur de service revient-il): Je ne suis pas sûr. Je sais que la classe ServiceController a une méthode WaitForState qui vous permet d'attendre jusqu'à ce que l'état donné soit atteint. Il est possible que le service le contrôleur attend un temps prédéterminé (un autre délai), puis met fin de force à votre application.

, Il est également très possible que la base.La méthode OnStop a été appelée, et la méthode OnStop est retournée, signalant au ServiceController que le processus s'est arrêté, alors qu'en fait il y a des threads qui ne se sont pas arrêtés. vous êtes responsable de termingating ces fils.

0
répondu Blue Toque 2009-10-06 21:58:44

Pour les personnes qui cherchent, comme moi, une solution pour raccourcir le temps de fermeture, essayez de définir le CloseTimeout de votre ServiceHost.

Maintenant, j'essaie de comprendre pourquoi il faut tellement de temps pour s'arrêter sans elle et je pense aussi que c'est un problème de threads. J'ai regardé dans Visual Studio, en m'attachant au service et en l'arrêtant : j'ai des threads lancés par mon service qui sont toujours en cours d'exécution.

Maintenant, la question Est : est-ce vraiment ces threads qui font que mon service s'arrête si lentement ? N'a pas Microsoft pense à ce sujet ? Ne pensez-vous pas que cela peut être un problème de libération de port ou autre chose ? Parce que c'est une perte de temps de gérer les threads sto et finalement ne pas avoir un temps de fermeture plus court.

0
répondu Chaos 2014-03-07 09:35:27

Matt Davis est assez complet.
Quelques points; Si vous avez un thread qui s'exécute pour toujours (parce qu'il a une boucle presque infinie et un catch all) et que le travail de votre service est d'exécuter ce thread, vous voulez probablement que ce soit un thread de premier plan.

En outre, si l'une de vos tâches effectue une opération plus longue telle qu'un appel sproc et que votre délai D'attente de jointure doit être un peu plus long, vous pouvez demander au SCM plus de temps pour arrêter. Voir: https://msdn.microsoft.com/en-us/library/system.serviceprocess.servicebase.requestadditionaltime(v=vs. 110).aspx Cela peut être utile pour éviter le statut redouté "marqué pour la suppression". Le maximum est défini dans le registre, donc je demande généralement le temps maximum attendu dans lequel le thread s'arrête habituellement (et jamais plus de 12s). Voir: Quelle est la durée maximale d'attente du service windows pour traiter la demande d'arrêt et comment demander du temps supplémentaire

Mon code ressemble à quelque chose comme:

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
       }
   }
}
0
répondu Lawrence Phillips 2017-05-23 12:01:40