2010-11-16 10 views
35

Ho ereditato un servizio Windows scritto in C#. In rare condizioni fallisce male. Tuttavia, non è affatto chiaro come fallire bene. Ross Bennett afferma elegantemente il problema allo bytes.com. Per semplicità, lo citerò qui.Qual è il modo corretto per il fallimento di un servizio di Windows?

Ahoy, Folks!

I've been looking all over for this, but I just can't seem to shake any documentation out of the MSDN or from Google. I've reviewed every .NET article on developing Windows Services in the MSDN I've located.

I'm developing a Windows Service application. This service reads its configuration data from the system registry (HKLM) where it was deposited by another "manager" application. No problems there.

The service uses a worker thread to do its work. The thread is created in the OnStart() and signaled/joined/disposed in the OnStop(). Again, no problems.

Everything works beautifully when:

  1. The system administrator has set up everything properly, and
  2. the foreign network resources are all reachable.

But of course, we as developers simply can't rely on:

  1. The system administrator having set up everything properly, or
  2. the foreign network resources being reachable.

Really, what we need is for the service application to have some way of dying on its own. If a network resource goes down, we need the service to stop. But more to the point, we need the SCM to know it has stopped on its own accord. SCM needs to know that the service has "failed"...and hasn't just been shut down by someone.

Calling "return" or throwing an exception in the "OnStart()" method isn't even helpful for services still in the start-up process.. The SCM goes merrily on and the process keeps running in the Task Manager--though it's not actually doing anything since the worker thread was never created and started.

Using a ServiceController instance doesn't do it, either. That appears to the SCM as a normal shutdown--not a service failure. So none of the recovery actions or restarts happen. (Also, there is MSDNful documentation warning about the perils of a ServiceBase descendant using a ServiceController to make things happen with itself.)

I've read articles where people were messing about with PInvoking calls to the native code just to set the "Stopped" status flag in the SCM. But that doesn't shut down the process the service is running within.

I'd really like to know the Intended Way of:

  1. Shutting down a service from within the service, where
  2. The SCM is appropriatedly notified that the service has "Stopped", and
  3. The process disappears from the Task Manager.

Solutions involving ServiceControllers don't seem to be appropriate, if only because 2 is not satisfied. (That the Framework documentation specifically contraindicates doing that carries a good deal of weight, incidentally.)

I'd appreciate any recommendations, pointers to documentation, or even well-reasoned conjecture. :-) Oh! And I'm perfectly happy to entertain that I've missed the point.

Most cordially,

Ross Bennett

risposta

27

La best practice nel codice nativo è chiamare SetServiceStatus con un codice di uscita diverso da zero per indicare 1) è interrotto e 2) qualcosa è andato storto.

Nel codice gestito, è possibile ottenere lo stesso effetto ottenendo l'handle SCM tramite the ServiceBase.ServiceHandle Property e P/Invokeing dell'API Win32.

Non vedo perché lo SCM lo tratti diversamente rispetto all'impostazione della proprietà ServiceBase.ExitCode diversa da zero e quindi chiama lo ServiceBase.Stop, in realtà. P/Invoke è forse un po 'più diretto, se il servizio è in modalità panico.

+16

La risposta che stavo cercando è contenuta in questa risposta. La risposta concisa è: Impostare la proprietà ServiceBase.ExitCode diversa da zero e quindi chiamare ServiceBase.Stop. –

+0

Grazie per aver chiarito cosa ha funzionato meglio. –

+4

Provato questo (sia tramite API gestita, sia tramite PInvoke di SetServiceStatus), e questo approccio non ha permesso al servizio di riavviarsi automaticamente se è configurato per farlo. Si ferma assolutamente il servizio però. Chiamare 'Environment.Exit()' o lanciare un'eccezione e non rilevarla causa la terminazione "anormale" che il sistema operativo sembra cercare, per il riavvio automatico dopo l'errore. – jglouie

5

Ho trovato che Environment.Exit (1) funziona bene per me. Generalmente lo inserisco in un metodo che rileva le eccezioni non gestite e registra il problema prima di interromperlo. Distrugge completamente il servizio, ma SCM sa anche che è in arresto. È possibile impostare SCM per riavviare il servizio automaticamente quando scende di un numero di volte. Trovo che sia molto più utile rispetto alla scrittura del proprio codice di riavvio/spegnimento.

+0

Environment.Exit() probabilmente funziona la maggior parte del tempo, ma si sta effettivamente prendendo a calci la sedia da sotto il vostro processo. Ad esempio, se si sta scrivendo un file, i buffer non verranno scaricati sul disco se si interrompe il processo. – ntcolonel

2

Non so se c'è un equivalente (non P/Invoke) per questo, ma il modo WinAPI sembra essere quello di chiamare SetServiceStatus con un valore di SERVICE_STOPPED e quindi attendere che SCM ti stacchi. Come effetto collaterale positivo, registra l'errore del servizio nel registro eventi.

Ecco alcune citazioni da the relevant part of the documentation:

If a service calls SetServiceStatus with the dwCurrentState member set to SERVICE_STOPPED and the dwWin32ExitCode member set to a nonzero value, the following entry is written into the System event log:

[...] <ServiceName> terminated with the following error: <ExitCode> [...]

The following are best practices when calling this function:

[...]

  • If the status is SERVICE_STOPPED, perform all necessary cleanup and call SetServiceStatus one time only. This function makes an LRPC call to the SCM. The first call to the function in the SERVICE_STOPPED state closes the RPC context handle and any subsequent calls can cause the process to crash.
  • Do not attempt to perform any additional work after calling SetServiceStatus with SERVICE_STOPPED, because the service process can be terminated at any time.

PS: A mio parere, se le risorse di rete non sono disponibili, il servizio non dovrebbe fermarsi ma continuare in esecuzione, in attesa che le risorse per diventare disponibili. Possono verificarsi interruzioni temporanee della rete e non è necessario richiedere l'intervento manuale dell'amministratore di sistema dopo il backup della rete.

+1

+1 che PS era proprio quello che stavo cercando. supponiamo che la chiave sia trovare un intervallo appropriato per verificare la disponibilità, quindi – LuqJensen

1

È possibile ottenere il codice di uscita corretto come descritto here. Quindi il Service Manager di Windows darà il giusto testo di errore.

mio OnStart in ServiceBase assomiglia a questo:

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(); 
    } 
} 
+1

La domanda a cui fai riferimento [Come ottenere codice di errore di eccezione in C#] (http://stackoverflow.com/questions/6893165/how-to-get-exception-error-code-in-c-sharp) spiega come ottenere il codice di eccezione quando si chiama una routine WMI tramite PInvoke. Non è in alcun modo una risposta generale a questa domanda. –

0

Dopo alcuni test ho trovato le seguenti opere in casi in cui si chiedono stop potrebbe causare altri problemi:

 ExitCode = 1; 
     Environment.Exit(1); 

Basta chiamare Environment.Exit non fa in modo che SCM gestisca i guasti, ma prima imposta ServiceCase ExitCode.

Problemi correlati