stop - ¿Cómo detener correctamente un servicio de Windows.NET multihebra?
windows 10 kill service (7)
El administrador de control de servicios (SCM) regresará cuando regrese de OnStop. Así que necesitas arreglar tu implementación de OnStop para bloquear hasta que todos los hilos hayan finalizado.
El enfoque general es hacer que OnStop señale a todos sus hilos que se detengan y luego espere a que se detengan. Para evitar el bloqueo indefinido, puede asignar a los subprocesos un tiempo límite para que se detengan y luego abortarlos si demoran demasiado.
Esto es lo que he hecho en el pasado:
- Cree un indicador bool global llamado Detener, establecido en falso cuando se inicia el servicio.
- Cuando se llama al método OnStop, establezca el indicador de parada en verdadero y luego haga un Thread.Join en todos los subprocesos de trabajo pendientes.
- Cada subproceso de trabajo es responsable de verificar el indicador de detención y salir de forma limpia cuando es verdadero. Esta comprobación debe realizarse con frecuencia, y siempre antes de una operación de larga duración, para evitar que demore el cierre del servicio durante demasiado tiempo.
- En el método OnStop, también tiene un tiempo de espera en las llamadas Unirse, para dar a los subprocesos un tiempo limitado para salir limpiamente ... después de lo cual simplemente abortar.
Tenga en cuenta que en el # 4 debe dar el tiempo adecuado para que sus hilos salgan en el caso normal. El aborto solo debe ocurrir en un caso inusual donde el hilo se cuelga ... en ese caso, hacer un aborto no es peor que si el usuario o el sistema interrumpen el proceso (este último si la computadora se apaga).
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). Cuando se intenta detener el servicio de Windows utilizando el complemento MSC de Servicios, la llamada para detener el servicio regresa con relativa rapidez, pero el proceso continúa ejecutándose durante unos 30 segundos aproximadamente.
La pregunta principal es cuál podría ser la razón por la que demoran más de 30 segundos en detenerse. ¿Qué puedo buscar y cómo lo busco?
La pregunta secundaria es por qué el servicio msc snap-in (controlador de servicio) está regresando aunque el proceso aún se está ejecutando. ¿Hay una manera de hacer que solo regrese cuando el proceso realmente se termina?
Aquí está 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 a las respuestas de limpieza de subprocesos
Muchos de ustedes han respondido que debería hacer un seguimiento de todos mis hilos y luego limpiarlos. No creo que sea un enfoque práctico. En primer lugar, no tengo acceso a todos los subprocesos administrados en una ubicación. El software es bastante grande con diferentes componentes, proyectos e incluso archivos DLL 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 marca que compruebe todos los hilos (incluso si pudiera hacer que todos los hilos verifiquen una bandera, muchos hilos están bloqueando cosas como los semáforos. Cuando están bloqueando, pueden No tengo que verificar. Tendré que hacer que esperen con un tiempo de espera, luego verifique esta bandera global y la espera de nuevo).
La bandera de IsBackround es una cosa interesante para comprobar. Sin embargo, una vez más, ¿cómo puedo saber si tengo subprocesos forground alrededor? Tendré que revisar cada sección del código que crea un hilo. ¿Hay alguna otra manera, tal vez una herramienta que pueda ayudarme a descubrir esto?
En última instancia, sin embargo, el proceso se detiene. Solo parecería que tengo que esperar algo. Sin embargo, si espero en el método OnStop durante X una cantidad de tiempo, el proceso tarda aproximadamente 30 segundos + X en detenerse. No importa lo que intente hacer, parece que el proceso necesita aproximadamente 30 segundos (no siempre son 30 segundos, puede variar) después de que OnStop vuelva para que el proceso se detenga.
La forma simple de hacer esto puede verse así:
-primero crete un evento global
ManualResetEvent shutdownEvent;
-en el inicio del servicio, cree el evento de restablecimiento manual y configúrelo en un estado inicial sin signo
shutdownEvent = new ManualResetEvent(false);
-en evento de parada de servicio
shutdownEvent.Set();
No olvides esperar al 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 );
-cada hilo debe probar de vez en cuando, el evento para detener
if ( shutdownEvent.WaitOne(delay, true) ) break;
La llamada para detener el servicio regresa tan pronto como su devolución de llamada OnStop()
regresa. Basado en lo que has mostrado, tu método OnStop()
no hace mucho, lo que explica por qué regresa tan rápido.
Hay un par de formas para hacer que su servicio termine.
Primero, puede volver a trabajar el método OnStop()
para indicar que todos los subprocesos se cierren y esperar a que se cierren antes de salir. Como @DSO sugirió, podría usar un indicador bool global para hacer esto (asegúrese de marcarlo como volatile
). Generalmente uso un ManualResetEvent, pero cualquiera de los dos funcionaría. Señala los hilos para salir. Luego, une los hilos con algún tipo de tiempo de espera (normalmente uso 3000 milisegundos). Si los subprocesos aún no han salido para entonces, 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 un gran problema. Si siempre tiene un hilo que debe abortarse, puede volver a trabajar ese hilo para responder mejor a su señal de apagado.
Segundo, marque sus hilos como hilos de background (vea here para más detalles). Parece que está utilizando la clase System.Threading.Thread para subprocesos, que son subprocesos de primer plano de forma predeterminada. Al hacer esto se asegurará de que los subprocesos no eviten que el proceso salga. Esto funcionará bien si solo está ejecutando código administrado. Si tiene un subproceso que está esperando en un código no administrado, no estoy seguro de si la configuración de la propiedad IsBackground todavía hará que el subproceso salga automáticamente al apagarse, es decir, es posible que aún tenga que volver a trabajar su modelo de subprocesos para que este subproceso responda a su solicitud de cierre.
Matt Davis es bastante completo.
Algunos puntos; Si tiene un subproceso que se ejecuta para siempre (porque tiene un bucle casi infinito y una captura de todos) y el trabajo de su servicio es ejecutar ese subproceso, es probable que desee que sea un subproceso en primer plano.
Además, si alguna de sus tareas está realizando una operación más prolongada, como una llamada extraviada, por lo que su tiempo de espera de Ingreso debe ser un poco más prolongado, puede solicitar al SCM más tiempo para que finalice el proceso. Consulte: 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 en el que el subproceso generalmente se cierra (y nunca más de 12 segundos). Vea: ¿cuál es el tiempo máximo de espera de servicio de Windows para procesar la solicitud de detención y cómo solicitar tiempo adicional?
Mi código se ve 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
}
}
}
Para las personas que buscan, como yo, una solución para acortar el tiempo de cierre, intente configurar CloseTimeout de su ServiceHost.
Ahora estoy tratando de entender por qué lleva tanto tiempo detenerse sin él y también creo que es un problema de hilos. Miré en Visual Studio, me conecté al servicio y lo detuve: tengo algunos subprocesos iniciados por mi servicio que aún se están ejecutando.
Ahora la pregunta es: ¿son realmente estos hilos los que hacen que mi servicio se detenga tan lentamente? ¿No lo pensó Microsoft? ¿No crees que puede ser un problema de liberación de puerto 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.
Para responder a la primera pregunta (¿Por qué el servicio continuaría funcionando durante más de 30 segundos)? Hay muchas razones. Por ejemplo, cuando se usa WCF, detener un Host hace que el proceso deje de aceptar solicitudes entrantes, y espera procesar todas las solicitudes actuales antes de detener.
Lo mismo sería válido para otros tipos de operaciones de red: las operaciones intentarán completarse antes de terminar. Esta es la razón por la que la mayoría de las solicitudes de red tienen un valor de tiempo de espera incorporado para cuando la solicitud se haya "bloqueado" (servidor inactivo, problemas de red, etc.).
Sin más información sobre qué es exactamente lo que está haciendo, no hay manera de decirle específicamente por qué tarda 30 segundos, pero es probable que sea un tiempo de espera.
Para responder a la segunda pregunta (¿Por qué está regresando 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 esté esperando un tiempo predeterminado (otro tiempo de espera) y luego finalice su aplicación a la fuerza.
También es muy posible que se haya llamado al método base.OnStop y que el método OnStop haya regresado, lo que indica al ServiceController que el proceso se ha detenido, cuando en realidad hay algunos subprocesos que no se han detenido. Usted es responsable de calificar estos hilos.
Señale la salida del bucle de sus subprocesos, hágalo limpio y haga unir Hilos de unión ... busque cuánto tiempo lleva como medida / cronómetro dónde están los problemas. Evite el cierre abortivo por varias razones.