2013-08-25 17 views
5

Quando si esegue tramite Gestione controllo servizi, i servizi Windows devono presupporre che i metodi di elaborazione dei comandi (OnStart, OnStop, ecc.) Possano essere richiamati su thread diversi senza nulla garantendo che, ad esempio, le assegnazioni ai membri siano visibili tra i metodi?I servizi di Windows devono garantire che i comandi possano essere elaborati su thread diversi?

public class MyService : ServiceBase { 

    private Object _foo;  

    protected override void OnStart(string[] args) { 
     _foo = new Object(); 
    } 

    protected override void OnStop() { 
     if (_foo == null) { 
      throw new Exception("Assignment not visible"); // Can this happen? 
     } 

     _foo = null; 
    } 

} 

non riesco a trovare una garanzia che l'eccezione nel mio esempio, non sarà gettato, ma tutti gli esempi che ho trovato, tra cui elsewhere on StackOverflow, sembra ritenere che, ad esempio, le assegnazioni a variabili in OnStart() sarà sempre visibile in OnStop().

Se SCM non fornisce tale garanzia, so come garantire che l'assegnazione sia visibile (ad esempio aggiungendo un blocco attorno a tutte le letture/scritture nel servizio). Mi interessa sapere se tali misure siano necessarie o meno.

+1

Sono interessato a quali sono le garanzie. Non ho mai visto qualcosa come il tuo esempio che causa un problema; tuttavia, so che acquisire risorse legate all'identità del thread (nel nostro caso un mutex) ha causato problemi alla mia squadra in passato. –

+2

Ho già fatto la stessa domanda. Lo scaverò. – spender

+1

Qui (contrassegnato come duplicato): [Calling ServiceBase.OnStart e OnStop ... stessa istanza?] (Http: // StackOverflow.it/questions/10799937/calling-servicebase-onstart-and-onstop-same-instance) – spender

risposta

0

l'esempio che ha dato non può in è accaduto sua ex solo molto molto improbabile:

_foo = new VeryLongAndTimeConsummingTask(); 

[EDIT]: di cui in commento SCM impedisce l'OnStop dalla esecuzione prima della OnStart completa. questo commento deriva dalla mia possibile cattiva pratica di esporre l'inizio e fermare i wrapper come pubblico.

se l'evento di arresto viene chiamato prima delle nuove finiture, può succedere che _foo sia nullo;

e anche _foo può essere rilasciato nel posto di aggiunta codice, quindi è buona norma controllare prima.

+0

"se l'evento stop viene chiamato prima che il' new' lo finisca può succedere che '_foo' è nullo;" Non è vero per l'esempio nella mia risposta. "e anche' _foo' può essere rilasciato in [un altro] posto nel codice, quindi [è] una buona pratica controllare prima. " Vero. – J0e3gan

+0

@ J0e3gan bel posto Ho pensato che OnStart e OnStop potessero essere chiamati da un posto diverso ma sì il dos SCM non ti permette di farlo casualmente. –

2

In un certo senso, SCM non può garantire che l'eccezione non verrà generata. Non controlla la manipolazione del servizio di un membro privato, naturalmente - ad es. se il codice di servizio aggiuntivo interessa _foo.

Detto questo, si consideri il seguente scenario per vedere il motivo per cui la risposta alla tua domanda specifica è chiaramente no:

1) Costruisci il tuo servizio con le seguenti modifiche di dimostrare:

public partial class MyService : ServiceBase 
    { 
     private Object _foo; 
     private const string _logName = "MyService Log.txt"; // to support added logging 

     public MyService() 
     { 
      InitializeComponent(); 
     } 

     protected override void OnStart(string[] args) 
     { 
      // demonstrative logging 
      var threadId = Thread.CurrentThread.ManagedThreadId; 
      using (var log = new StreamWriter(AppDomain.CurrentDomain.BaseDirectory + _logName, true)) 
      { 
       log.WriteLine("{0}: In OnStart(string[]) on thread ID {1}. Sleeping for 10 seconds...", DateTime.Now, threadId); 
      } 

      // Sleep before initializing _foo to allow calling OnStop before OnStart completes unless the SCM synchronizes calls to the methods. 
      Thread.Sleep(10000); 

      _foo = new Object(); 
     } 

     protected override void OnStop() 
     { 
      // demonstrative logging added 
      var threadId = Thread.CurrentThread.ManagedThreadId; 
      using (var log = new StreamWriter(AppDomain.CurrentDomain.BaseDirectory + _logName, true)) 
      { 
       log.WriteLine("{0}: In OnStop() on thread ID {1}.", DateTime.Now, threadId); 
      } 

      if (_foo == null) 
      { 
       // demonstrative logging added 
       using (var log = new StreamWriter(AppDomain.CurrentDomain.BaseDirectory + _logName, true)) 
       { 
        log.WriteLine("{0}: _foo == null", DateTime.Now); 
       } 

       throw new Exception("Assignment not visible"); // Can this happen? 
      } 

      _foo = null; 
     } 
    } 

2) Apri una shell di comandi.

3) Aprire un'altra shell di comandi.

4) Nella prima shell di comando, installare il servizio (con sc create) se non lo si è già fatto e avviarlo (con net start). Si dovrebbe vedere:

Il servizio MyService sta iniziando .....

I puntini di trascinamento dovrebbe essere aggiunto uno per uno come l'SCM attende attraverso i 10 secondi di dormire per avviare il servizio.

5) Nella seconda shell di comando, provare a interrompere il servizio (con net stop) prima che siano trascorsi 10 secondi. Dovresti vedere:

Il servizio è in corso o in arresto. Per favore riprova più tardi.

Quindi, l'avvio di un servizio è chiaramente un'operazione di blocco che deve essere completata prima che il servizio possa essere interrotto.

6) Controllare la prima shell di comando dopo che sono trascorsi 10 secondi. Si dovrebbe vedere: servizio

La MyService è stato avviato con successo.

7) Ritornare alla seconda shell di comando e provare a interrompere nuovamente il servizio. Dovresti vedere:

Il servizio MyService è in fase di arresto.

Il servizio MyService è stato arrestato correttamente.

8) il registro risultante - per esempio:

10/22/2013 7:28:55 AM: In OnStart (stringa []) su ID filo 4. dormitorio per 10 secondi ...

10/22/2013 7:29:17 AM: In OnStop() sul thread ID 5.

Avvio e arresto del servizio in modo rapido è più facile con due gusci di comando che penso; ma l'esempio funziona in modo simile anche con una shell di comando.

Infine, si potrebbe trovare Mitchell Taylor (CoolDadTx) 's risposta a a similar question in MSDN forums interessante come ho fatto io:

Il modello di threading utilizzato dal SCM non è documentato formalmente per quanto ne so. Ciò che è noto è che ogni servizio viene chiamato sul proprio thread. Tuttavia, SCM potrebbe o non potrebbe utilizzare un pool di thread per riutilizzare un thread tra i servizi. Quando viene chiamato un servizio (start, stop, comandi personalizzati, ecc.) È previsto che esegua il proprio compito e ritorni rapidamente. C'è un forte limite per quanto tempo ci vuole. Qualcosa di più di un rapido ritorno richiede che tu spinga la richiesta su un thread secondario per l'elaborazione. Lo SCM stesso viene eseguito su un thread separato, pertanto se un servizio richiede troppo tempo per rispondere, SCM lo vede bloccato. Questo è discusso qui: http://msdn.microsoft.com/en-us/library/ms684264(VS.85).aspx

UPDATE:

nota particolare l'articolo Service State Transitions MSDN cui l'articolo che Mitchell Taylor cita pubblicitari. Contiene un diagramma di stato che in modo piuttosto chiaro, &, documenta in modo autorevole le transizioni dello stato del servizio definite e si allinea con ciò che ho delineato sopra. Spiega anche in relazione al diagramma di stato come l'SCM non trasmetta a volte richieste di controllo di servizio per assicurare solo transizioni di stato definite.

Problemi correlati