2011-12-01 24 views
6

Tengo un proceso que se ejecuta en su propio hilo y puede iniciarse/detenerse sin bloqueo. Eventualmente, esto irá a un servicio de Windows, pero estoy configurando esto en una aplicación de consola por ahora hasta que esté completamente desarrollado.Usar un semáforo en lugar del ciclo while. ¿Esto es bueno o malo?

Después de la llamada a Inicio(), quiero que el hilo del programa principal se bloquee hasta que se presione Ctrl-C. Sé que esto funcionará:

public static void Main(string[] args) 
{ 
    bool keepGoing = true; 

    var service = new Service(); 

    System.Console.TreatControlCAsInput = false; 

    System.Console.CancelKeyPress += delegate(object sender, ConsoleCancelEventArgs e) 
    { 
     e.Cancel = true; 
     service.Stop(); 
     keepGoing = false; // Break the while loop below 
    }; 

    service.Start(); 

    while(keepGoing) 
    { 
     Thread.Sleep(100); // 100 is arbitrary 
    } 

} 

Sin embargo, creo que el indicador y el valor de suspensión arbitrario son molestos. Sé que el costo de la CPU es prácticamente 0 en el ciclo while, pero prefiero tener un bloque "difícil" que se libera tan pronto como finaliza el controlador Ctrl-C. Ideé el siguiente, usando un semáforo para bloquear hasta que se haga el manejador anónima Ctrl-C:

public static void Main(string[] args) 
{ 
    var service = new Service(); 

    var s = new Semaphore(1, 1); 

    System.Console.TreatControlCAsInput = false; 

    System.Console.CancelKeyPress += delegate(object sender, ConsoleCancelEventArgs e) 
    { 
     e.Cancel = true; 
     service.Stop(); 
     s.Release(); // This will allow the program to conclude below 
    }; 

    service.Start(); 

    s.WaitOne(); // This will not block 
    s.WaitOne(); // This will block w/o CPU usage until the sempahore is released 

} 

¿Es un mal diseño? ¿Es excesivo? ¿Es peligroso?

EDIT:

también puedo conectar AppDomain.CurrentDomain.UnhandledException de la siguiente manera:

AppDomain.CurrentDomain.UnhandledException += delegate { 
    service.Stop(); 
    s.Release(); 
}; 

editar el segundo:

Debo señalar que es fundamental que la Se llama al método Stop() al salir. @Adam Ralph tiene un patrón perfectamente bueno para una consola/servicio híbrido, pero no tenía esta información al responder la Q.

+1

Si puede evitar el ciclo while digo que vale la pena seguir. – ChaosPandion

+1

Para la creación de prototipos esto último es una gran mejora. En una aplicación de producción, evitaría toda la idea de romper "CTRL + C". Use una señal para matar el hilo en su lugar. La forma en que la señal es 'Set()' depende de las otras capas involucradas. Solo téngalo en cuenta al diseñar su servicio. Agregue un medio, como un método al que se pueda llamar, a su vez, invoque 'Set()'. –

+0

@ P.Brian.Mackey: aunque esta no es una aplicación de producción, si * fuera *, ¿no sería prudente al menos manejar Ctrl-C correctamente en una aplicación de consola no interactiva? Tal como está, Ctrl-C simplemente cerrará el programa sin una oportunidad de limpieza antes de salir. En última instancia, simplemente me preguntaba si la solución de semáforo a la opción "while (flag) Thread.Sleep (...)". –

Respuesta

4

Tenemos un requisito similar en algunas de nuestras aplicaciones. Son servicios de Windows, pero para la depuración a menudo queremos ejecutarlos como aplicaciones de consola. Además, usualmente codificamos aplicaciones nuevas como servicios de Windows desde el principio, pero a menudo no queremos tener que ejecutarlas como servicio hasta más adelante, una vez que hemos probado el concepto, etc.

Este es el patrón que uso: -

using (var service = new Service()) 
{ 
    if (Environment.UserInterActive) 
    { 
     service.Start(); 
     Thread.Sleep(Timeout.Infinite); 
    } 
    else 
    { 
     ServiceBase.Run(service); 
    } 
} 

Contar el hilo a dormir infinitamente podría parecer ineficiente, pero esto es sólo para los escenarios de depuración y el hilo redundante cuesta ningún tiempo de CPU, sólo algunas de memoria (alrededor de 1 MB), que se compone sobre todo de la espacio de pila asignado a la secuencia. El proceso aún puede salir con Ctrl + C o al cerrar la ventana de comandos.

- EDITAR -

Si encuentra que service.Dispose() no se está llamando cuando se pulsa Ctrl + C (es decir, un aborto ocurre grosero) y la llamada a Dispose() es crucial, entonces supongo que se podría hacer de forma explícita este modo: -

using (var service = new Service()) 
{ 
    if (Environment.UserInterActive) 
    { 
     Console.CancelKeyPress += (sender, e) => service.Dispose(); 
     service.Start(); 
     Thread.Sleep(Timeout.Infinite); 
    } 
    else 
    { 
     ServiceBase.Run(service); 
    } 
} 

Tenga en cuenta que Stop() debe encapsularse en Dispose().

+0

¿Esto todavía no tiene el problema de que, en un contexto 'UserInterActive', si el usuario intentara detener el proceso (usando Ctrl-C o cierre de ventana), no se llamaría al método' Stop() ' ? Esto es lo que trato de lograr en mi ejemplo anterior. –

+0

En nuestros servicios, llamar al método Stop no es crucial, pero supongo que aún podría suscribirse a CancelKeyPress y hacerlo explícitamente. Editaré la respuesta –

+0

No tenía claro esto en la Q; lo siento por eso. Edité mencionando esto. –

Cuestiones relacionadas