2009-10-06 27 views
21

Tengo un servicio de Windows escrito en C# que crea una carga de subprocesos y hace muchas conexiones de red (WMI, SNMP, TCP simple, http). Al intentar detener el servicio de Windows utilizando el complemento Services MSC, la llamada para detener el servicio vuelve relativamente rápido pero el proceso continúa ejecutándose durante aproximadamente 30 segundos más o menos.¿Cómo detener correctamente un servicio de Windows .NET multiproceso?

La pregunta principal es cuál podría ser el motivo por el que tarda más de 30 segundos en detenerse. ¿Qué puedo buscar y cómo puedo buscarlo?

La pregunta secundaria es por qué es el complemento msc de servicio (controlador de servicio) que vuelve aunque el proceso todavía se está ejecutando. ¿Hay alguna manera de lograr que solo regrese cuando el proceso es realmente asesinado?

Este es el código en el método OnStop del servicio

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

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

    base.OnStop(); 

    //doing some tracing here 
} 

Editar en respuesta al hilo de limpieza responde

Muchos de ustedes han respondido que debería realizar un seguimiento de todos mis hilos y luego límpialos. No creo que sea un enfoque práctico. En primer lugar, no tengo acceso a todos los hilos gestionados en una ubicación. El software es bastante grande con diferentes componentes, proyectos e incluso dlls de terceros que podrían estar creando subprocesos. No hay forma de que pueda hacer un seguimiento de todos ellos en una ubicación o tener una bandera que comprueben todos los hilos (incluso si pudiera hacer que todos los hilos revisaran una bandera, muchos hilos están bloqueando cosas como semáforos. Cuando están bloqueando pueden Verifique. Tendré que hacer que esperen con un tiempo de espera, luego verifique esta bandera global y vuelva a esperar).

La bandera de IsBackround es algo interesante de comprobar. Sin embargo, una vez más, ¿cómo puedo averiguar si tengo algún hilo forground corriendo alrededor? Tendré que verificar cada sección del código que crea un hilo. ¿Hay alguna otra manera, tal vez una herramienta que puede ayudarme a descubrir esto?

Al final, el proceso se detiene. Parecería que necesito esperar algo. Sin embargo, si espero en el método OnStop durante X cantidad de tiempo, el proceso tarda aproximadamente 30 segundos + X en detenerse. No importa lo que intento hacer, parece que el proceso necesita aproximadamente 30 segundos (no siempre son 30 segundos, puede variar) después de que OnStop regrese para que el proceso se detenga.

+0

¿Ha puesto en nada para que los otros hilos se detienen de manera adecuada? ¿Son hilos de fondo o hilos de primer plano? –

+3

Si está utilizando componentes que pueden crear subprocesos internamente, lo ideal es que expongan un mecanismo de apagado adecuado que pueda invocar en OnStop, para que no tenga que administrar sus subprocesos directamente. Si no es así, o si no desea preocuparse por la salida limpia y desea que el proceso finalice inmediatamente, intente llamar a Environment.Exit ... sin embargo, no estoy seguro de cómo reaccionará el SCM cuando el servicio termine mientras lo está enviando un comando de parada. – DSO

Respuesta

15

La llamada para detener el servicio vuelve tan pronto como regrese su devolución de llamada OnStop(). Según lo que ha mostrado, su método OnStop() no hace mucho, lo que explica por qué regresa tan rápido.

Hay un par de maneras de hacer que su servicio salga.

Primero, puede volver a trabajar el método OnStop() para señalar todos los hilos para cerrar y esperar a que se cierren antes de salir. Como lo sugirió @DSO, podría usar un indicador bool global para hacer esto (asegúrese de marcarlo como volatile). Generalmente uso un ManualResetEvent, pero cualquiera funcionaría. Señale los hilos para salir. Luego, únete a los hilos con algún tipo de período de tiempo de espera (usualmente uso 3000 milisegundos). Si los hilos todavía no han salido antes, puede llamar al método Abort() para salir de ellos. En general, el método Abort() está mal visto, pero dado que su proceso está saliendo de todos modos, no es gran cosa. Si constantemente tiene un hilo que debe abortarse, puede volver a trabajar ese hilo para que responda mejor a su señal de apagado.

En segundo lugar, marque sus hilos como background hilos (vea here para más detalles). Parece que está utilizando la clase System.Threading.Thread para hilos, que son hilos de primer plano por defecto. Al hacer esto, se asegurará de que los hilos no impidan que el proceso se cierre. Esto funcionará bien si solo está ejecutando código administrado.Si tiene un hilo que está esperando un código no administrado, no estoy seguro de si la configuración de la propiedad IsBackground causará que el hilo salga automáticamente al apagarlo, es decir, es posible que aún tenga que volver a trabajar su modelo de subprocesamiento para que este hilo responda a su solicitud de cierre.

+0

Acepté esta respuesta porque mencionaba la propiedad del hilo IsBackground. Eso era lo único que necesitaba cambiar. No creo en la creación de un pabellón global que todos y cada uno de los componentes deban usar, eso es demasiado en mi opinión. Sin embargo, si los hilos están marcados correctamente como hilos de fondo, el servicio se detiene bien. – Mark

+1

No usaría la bandera/evento global tampoco. Lo que hice fue crear un contenedor alrededor de un objeto System.Threading.Thread. El constructor de este contenedor crea el hilo, establece el nombre y establece la propiedad IsBackground. Tengo métodos públicos para iniciar y detener el hilo. El método Stop(), en particular, establece un ManualResetEvent privado que indica al hilo que deje de ejecutarse. Para hacerlo completamente flexible, el constructor acepta lo que equivale a un delegado System.Threading.ThreadStart, permitiendo a cualquier persona usar esta clase sin tener que heredar de ella. –

10

El administrador de control de servicio (SCM) volverá cuando regrese de OnStop. Entonces, debe arreglar su implementación OnStop para bloquear hasta que todos los hilos hayan terminado.

El enfoque general es hacer que OnStop señalice todos sus hilos para detenerlos, y luego espere a que se detengan. Para evitar el bloqueo de forma indefinida, puede dar a los hilos un límite de tiempo para detenerlos, luego abortarlos si tardan demasiado.

Esto es lo que he hecho en el pasado:

  1. Crear una bandera bool global llamada parada, se pone a falso cuando se inicia el servicio .
  2. Cuando se llama al método OnStop, establezca el indicador de Parada en verdadero y luego realice un Subproceso. Participe en todos los subprocesos de trabajo pendientes.
  3. Cada subproceso de trabajador es responsable de comprobar la marca de detención y salir limpiamente cuando es verdadero. Esta comprobación debe realizarse con frecuencia, y siempre antes de una operación de larga ejecución, para evitar que retrase el cierre del servicio durante demasiado tiempo.
  4. En el método OnStop, también tienen un tiempo de espera en las llamadas a la unión, para dar a los hilos un tiempo limitado para salir limpiamente ... después de lo cual, simplemente lo cancela.

Nota en el n. ° 4 se debe dar el tiempo adecuado para que salgan los hilos en caso normal. El aborto solo debería ocurrir en casos inusuales en los que se cuelgue hilo ... en ese caso, realizar un aborto no es peor que si el usuario o el sistema mata el proceso (este último si la computadora se está apagando).

+1

+1, no hay forma de resolver este problema hasta que sepa qué están haciendo todos sus componentes y tenga alguna forma de unirse (y finalizar) sus operaciones de ejecución prolongada en diferentes subprocesos.Puede tratar con el bloqueo de semáforos estableciendo un tiempo de espera en sus operaciones de Espera, ejecutando la espera dentro de un bucle y comprobando que su indicador de apagado sea la condición de salida del bucle. –

0

Señale la salida del bucle de threads, hágalo limpio y realice el thread Join-s .. busque cuánto tiempo lleva como medida/cronómetro dónde están los problemas. Evite el apagado fallido por varias razones.

0

Para responder la primera pregunta (¿Por qué el servicio continuará funcionando durante más de 30 segundos): hay muchas razones. Por ejemplo, al usar WCF, detener un host hace que el proceso deje de aceptar solicitudes entrantes y espera procesar todas las solicitudes actuales antes de detenerse.

Lo mismo ocurriría con otros tipos de operaciones de red: las operaciones intentarían completarse antes de terminar. Esta es la razón por la cual la mayoría de las solicitudes de red tienen un valor de tiempo de espera incorporado para cuando la solicitud puede haberse "bloqueado" (servidor inactivo, problemas de red, etc.).

Sin más información sobre qué es exactamente lo que está haciendo, no hay forma de decirle específicamente por qué tarda unos 30 segundos, pero probablemente sea un tiempo de espera.

Para responder la segunda pregunta (¿Por qué regresa el controlador de servicio?): No estoy seguro. Sé que la clase ServiceController tiene un método WaitForState que le permite esperar hasta que se alcance el estado dado. Es posible que el controlador de servicio espere un tiempo predeterminado (otro tiempo de espera) y luego finalice forzosamente su aplicación.

También es muy posible que se haya llamado al método base.OnStop y haya regresado el método OnStop, indicando al ServiceController que el proceso se ha detenido, cuando en realidad hay algunos subprocesos que no se han detenido. usted es responsable de la unión de estos hilos.

1

La forma más simple de hacer esto puede tener este aspecto:
Creta -primero un evento mundial

ManualResetEvent shutdownEvent;

servicio -al empezar crear el evento de restablecimiento manual y la puso a un estado inicial de unsignaled

shutdownEvent = new ManualResetEvent(false);

parada de servicio -en caso

shutdownEvent.Set();

no se olvide de esperar hasta el final de los hilos

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

hilo -cada debe probar de vez en cuando, el evento dejará de

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

Para las personas que buscan, como yo, de una solución a corto es el tiempo de cierre, tratar de establecer el CloseTimeout de su ServiceHost.

Ahora estoy tratando de entender por qué toma tanto tiempo parar sin él y también creo que es un problema de hilos. Lo busqué en Visual Studio, adjuntándome al servicio y deteniéndolo: tengo algunos hilos lanzados por mi servicio que aún se están ejecutando.

Ahora la pregunta es: ¿realmente son estos hilos los que hacen que mi servicio se detenga tan lentamente? ¿Microsoft no lo pensó? ¿No crees que puede ser un problema de liberación de puertos o algo más? Porque es una pérdida de tiempo manejar los hilos y finalmente no tener un tiempo de cierre más corto.

0

Matt Davis es bastante completo.
Algunos puntos; Si tiene un hilo que se ejecuta para siempre (porque tiene un bucle casi infinito y lo atrapa todo) y el trabajo de su servicio es ejecutar ese hilo, probablemente desee que sea un hilo de primer plano.

Además, si alguna de sus tareas realiza una operación más larga como una llamada sproc y por lo tanto su tiempo de espera para unirse necesita ser un poco más largo, puede pedirle al SCM más tiempo para cerrar. Ver: https://msdn.microsoft.com/en-us/library/system.serviceprocess.servicebase.requestadditionaltime(v=vs.110).aspx Esto puede ser útil para evitar el temido estado "marcado para eliminación". El máximo se establece en el registro, por lo que normalmente solicito el tiempo máximo esperado que el hilo normalmente cierra (y nunca más de 12 segundos). Ver: what is the maximum time windows service wait to process stop request and how to request for additional time

Mi código es algo como:

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 
     } 
    } 
} 
Cuestiones relacionadas