J'ai hérité d'un service Windows écrit en C #. Dans de rares cas, il échoue mal. Cependant, il est difficile de savoir comment bien échouer. Ross Bennett expose le problème avec élégance à bytes.com . Par souci de simplicité, je vais simplement le citer ici.
Ahoy, les gens!
Je cherchais tout cela, mais je n'arrive pas à supprimer la documentation de MSDN ou de Google. J'ai examiné tous les articles .NET sur le développement de services Windows dans le MSDN que j'ai localisé.
Je développe une application de service Windows. Ce service lit ses données de configuration à partir du registre système (HKLM) où elles ont été déposées par une autre application "manager". Pas de problèmes là-bas.
Le service utilise un thread de travail pour effectuer son travail. Le thread est créé dans OnStart () et signalé/joint/disposé dans OnStop (). Encore une fois, pas de problèmes.
Tout fonctionne à merveille quand:
- L’administrateur système a tout configuré correctement et
- les ressources du réseau étranger sont toutes accessibles.
Mais bien sûr, nous, les développeurs, ne pouvons tout simplement pas compter sur:
- L'administrateur système ayant tout configuré correctement ou
- les ressources du réseau étranger étant joignables.
En réalité, ce dont nous avons besoin, c’est que l’application de service dispose d’un moyen de mourir par elle-même. Si une ressource réseau tombe en panne, nous devons arrêter le service. Mais plus précisément, nous avons besoin que le SCM sache qu'il s'est arrêté de lui-même. SCM doit savoir que le service a "échoué" ... et que quelqu'un ne l'a pas arrêté.
Le fait d'appeler "return" ou de lancer une exception dans la méthode "OnStart ()" n'est même pas utile pour les services encore dans le processus de démarrage. Le GDS se déroule normalement et le processus continue de s'exécuter dans le Gestionnaire des tâches, bien qu'il ne fait rien du tout puisque le thread de travail n'a jamais été créé ni démarré.
L'utilisation d'une instance ServiceController ne le fait pas non plus. Cela apparaît au SCM comme un arrêt normal et non comme une panne de service. Donc, aucune des actions de récupération ou de redémarrage ne se produit. (En outre, la documentation MSDNful avertit des dangers d'un descendant de ServiceBase en utilisant un ServiceController pour que les choses se passent de lui-même.)
J'ai lu des articles dans lesquels des personnes discutaient avec des appels PInvoking au code natif simplement pour définir l'indicateur d'état "Stopped" dans le SCM. Mais cela n'arrête pas le processus dans lequel le service est exécuté.
J'aimerais vraiment connaître le moyen envisagé de:
- Fermer un service depuis le service, où
- Le SCM est informé de manière appropriée que le service est "arrêté", et
- Le processus disparaît du gestionnaire de tâches.
Les solutions impliquant ServiceControllers ne semblent pas appropriées, ne serait-ce que parce que 2 n'est pas satisfait. (Incidemment, la documentation du Framework contre-indique cette pratique qui a beaucoup de poids.)
J'apprécierais toutes les recommandations, les pointeurs de documentation, ou même des conjectures bien raisonnées. :-) Oh! Et je suis parfaitement heureux de divertir que j'ai manqué le point.
Bien cordialement
Ross Bennett
La meilleure pratique en code natif consiste à appeler SetServiceStatus avec un code de sortie différent de zéro pour indiquer 1) l’arrêt et 2) quelque chose qui ne va pas.
En code managé, vous pouvez obtenir le même effet en obtenant le descripteur SCM via la propriété ServiceBase.ServiceHandle et P/Invoke-ing de l'API Win32.
Je ne vois pas pourquoi le SCM traiterait cela différemment de la définition de la propriété ServiceBase.ExitCode
non nulle et de l'appel de ServiceBase.Stop
, en fait. P/Invoke est peut-être un peu plus direct si le service est en mode panique.
J'ai trouvé que Environment.Exit (1) fonctionne bien pour moi. Je le place généralement dans une méthode qui intercepte les exceptions non gérées et enregistre le problème avant de l'arrêter. Il détruit complètement le service, mais le SCM sait également qu'il est arrêté. Vous pouvez configurer le SCM pour qu'il redémarre automatiquement votre service lorsqu'il tombe en panne x fois. Je trouve cela beaucoup plus utile que d’écrire votre propre code de redémarrage/arrêt.
Je ne sais pas s'il existe un équivalent (autre que P/Invoke) pour cela, mais la méthode WinAPI semble être d'appeler SetServiceStatus
avec une valeur de SERVICE_STOPPED
et d'attendre que le SCM vous ferme. En tant qu'effet secondaire positif, il enregistre la défaillance de votre service dans le journal des événements.
Voici quelques citations de la partie pertinente de la documentation :
Si un service appelle SetServiceStatus avec le membre dwCurrentState défini sur SERVICE_STOPPED et le membre dwWin32ExitCode sur une valeur différente de zéro, l'entrée suivante est écrite dans le journal des événements système:
[...] <NomService> s'est terminé avec l'erreur suivante: <ExitCode> [...]
Les meilleures pratiques à suivre pour appeler cette fonction sont les suivantes:
[...]
- Si le statut est SERVICE_STOPPED, effectuez tous les nettoyages nécessaires et appelez SetServiceStatus une seule fois. Cette fonction fait un appel LRPC au GDS. Le premier appel de la fonction dans l'état SERVICE_STOPPED ferme le descripteur de contexte RPC et tout appel ultérieur peut provoquer le blocage du processus.
- N'essayez pas d'exécuter des tâches supplémentaires après avoir appelé SetServiceStatus avec SERVICE_STOPPED, car le processus de service peut être interrompu à tout moment.
PS: À mon avis, si les ressources du réseau sont indisponibles, le service ne doit pas s’arrêter mais continuer à fonctionner, en attendant que les ressources deviennent disponibles. Des pannes réseau temporaires peuvent survenir et elles ne devraient pas nécessiter d'intervention manuelle de la part de l'administrateur système une fois le réseau sauvegardé.
Après quelques tests, j’ai trouvé les solutions suivantes dans les cas où vous appelez Stop pouvait entraîner d’autres problèmes:
ExitCode = 1;
Environment.Exit(1);
Le simple fait d'appeler Environment.Exit ne permet pas au SCM de gérer les erreurs, mais vous devez d'abord définir le ServiceBase ExitCode.
Vous pouvez obtenir le code ExitCode approprié comme décrit ici . . Ainsi, le gestionnaire de services Windows donnera le texte d'erreur correct.
Mon OnStart dans ServiceBase ressemble à ceci:
protected override void OnStart(string[] args)
{
try
{
DoStart();
}
catch (Exception exp)
{
Win32Exception w32ex = exp as Win32Exception;
if (w32ex == null)
{
w32ex = exp.InnerException as Win32Exception;
}
if (w32ex != null)
{
ExitCode = w32ex.ErrorCode;
}
Stop();
}
}