¿Cómo mantener funcionando una aplicación de consola .NET?

104

Considere una aplicación de consola que inicia algunos servicios en un hilo separado. Todo lo que necesita hacer es esperar a que el usuario presione Ctrl + C para apagarlo.

¿Cuál de las siguientes es la mejor manera de hacer esto?

static ManualResetEvent _quitEvent = new ManualResetEvent(false);

static void Main() {
    Console.CancelKeyPress += (sender, eArgs) => {
        _quitEvent.Set();
        eArgs.Cancel = true;
    };

    // kick off asynchronous stuff 

    _quitEvent.WaitOne();

    // cleanup/shutdown and quit
}

O esto, usando Thread.Sleep (1):

static bool _quitFlag = false;

static void Main() {
    Console.CancelKeyPress += delegate {
        _quitFlag = true;
    };

    // kick off asynchronous stuff 

    while (!_quitFlag) {
        Thread.Sleep(1);
    }

    // cleanup/shutdown and quit
}
en órbita
fuente

Respuestas:

63

siempre desea evitar el uso de bucles while, especialmente cuando obliga al código a volver a verificar las variables. Desperdicia los recursos de la CPU y ralentiza su programa.

Definitivamente diría el primero.

Miguel
fuente
2
+1. Además, dado boolque no se declara como volatile, existe la posibilidad definitiva de que las lecturas posteriores _quitFlagen el whileciclo se optimicen, lo que lleva a un ciclo infinito.
Adam Robinson
2
Falta la forma recomendada de hacer esto. Esperaba esto como respuesta.
Iúri dos Anjos
30

Alternativamente, una solución más simple es simplemente:

Console.ReadLine();
Cocowalla
fuente
Estaba a punto de sugerir eso, pero no se detendrá solo en Ctrl-C
Thomas Levesque
Tengo la impresión de que CTRL-C era solo un ejemplo, cualquier entrada del usuario para cerrarlo
Cocowalla
Recuerde que 'Console.ReadLine ()' bloquea el hilo. Entonces, la aplicación aún estaría ejecutándose pero sin hacer nada más que esperar que el usuario ingrese una línea
fabriciorissetto
2
@fabriciorissetto La pregunta de OP dice 'iniciar cosas asincrónicas', por lo que la aplicación realizará un trabajo en otro hilo
Cocowalla
1
@Cocowalla Me perdí eso. ¡Culpa mía!
fabriciorissetto
12

Puede hacer eso (y eliminar el CancelKeyPresscontrolador de eventos):

while(!_quitFlag)
{
    var keyInfo = Console.ReadKey();
    _quitFlag = keyInfo.Key == ConsoleKey.C
             && keyInfo.Modifiers == ConsoleModifiers.Control;
}

No estoy seguro si eso es mejor, pero no me gusta la idea de llamar Thread.Sleepen un bucle ... Creo que es más limpio bloquear la entrada del usuario.

Thomas Levesque
fuente
No me gusta que estés buscando las teclas Ctrl + C, en lugar de la señal activada por Ctrl + C.
CodesInChaos
9

Prefiero usar Application.Run

static void Main(string[] args) {

   //Do your stuff here

   System.Windows.Forms.Application.Run();

   //Cleanup/Before Quit
}

de los documentos:

Comienza a ejecutar un bucle de mensajes de aplicación estándar en el hilo actual, sin un formulario.

ygaradon
fuente
9
Pero luego toma una dependencia en formularios de Windows solo para esto. No es un gran problema con el marco .NET tradicional, pero la tendencia actual es hacia implementaciones modulares que incluyen solo las partes que necesita.
CodesInChaos
4

Parece que lo estás poniendo más difícil de lo necesario. ¿Por qué no solo Joinel hilo después de haber señalado que se detenga?

class Program
{
    static void Main(string[] args)
    {
        Worker worker = new Worker();
        Thread t = new Thread(worker.DoWork);
        t.IsBackground = true;
        t.Start();

        while (true)
        {
            var keyInfo = Console.ReadKey();
            if (keyInfo.Key == ConsoleKey.C && keyInfo.Modifiers == ConsoleModifiers.Control)
            {
                worker.KeepGoing = false;
                break;
            }
        }
        t.Join();
    }
}

class Worker
{
    public bool KeepGoing { get; set; }

    public Worker()
    {
        KeepGoing = true;
    }

    public void DoWork()
    {
        while (KeepGoing)
        {
            Console.WriteLine("Ding");
            Thread.Sleep(200);
        }
    }
}
Ed Power
fuente
2
En mi caso, no controlo los hilos en los que se ejecuta el material asincrónico.
intoOrbit
1) No me gusta que esté buscando las teclas Ctrl + C, en lugar de la señal activada por Ctrl + C. 2) Su enfoque no funciona si la aplicación usa Tareas en lugar de un solo hilo de trabajo.
CodesInChaos
2

También es posible bloquear el hilo / programa basándose en un token de cancelación.

token.WaitHandle.WaitOne();

WaitHandle se señala cuando se cancela el token.

He visto esta técnica utilizada por Microsoft.Azure.WebJobs.JobHost, donde el token proviene de una fuente de token de cancelación de WebJobsShutdownWatcher (un observador de archivos que finaliza el trabajo).

Esto le da cierto control sobre cuándo puede finalizar el programa.

CRice
fuente
1
Esta es una excelente respuesta para cualquier aplicación de consola del mundo real que necesite escuchar CTL+Cporque está realizando una operación de larga duración, o es un demonio, que también debería cerrar correctamente sus subprocesos de trabajo. Haría eso con un CancelToken y, por lo tanto, esta respuesta aprovecha una WaitHandleque ya existiría, en lugar de crear una nueva.
mdisibio
1

De los dos el primero es mejor

_quitEvent.WaitOne();

porque en el segundo, el hilo se despierta cada milisegundo y se convertirá en una interrupción del sistema operativo, que es costosa

Naveen
fuente
Esta es una buena alternativa para los Consolemétodos si no tiene una consola adjunta (porque, por ejemplo, el programa es iniciado por un servicio)
Marco Sulla
0

Debe hacerlo como lo haría si estuviera programando un servicio de Windows. Nunca usarías una declaración while en lugar de eso, usarías un delegado. WaitOne () generalmente se usa mientras se espera que los hilos se eliminen - Thread.Sleep () - no es aconsejable - ¿Ha pensado en usar System.Timers.Timer usando ese evento para verificar el evento de apagado?

KenL
fuente