Como manter um aplicativo de console .NET em execução?

104

Considere um aplicativo de console que inicializa alguns serviços em um thread separado. Tudo o que precisa fazer é esperar que o usuário pressione Ctrl + C para desligá-lo.

Qual das alternativas a seguir é a melhor maneira de fazer isso?

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
}

Ou isso, 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
}
intoOrbit
fonte

Respostas:

63

você sempre deseja evitar o uso de loops while, especialmente quando está forçando o código a verificar novamente as variáveis. Ele desperdiça recursos da CPU e retarda seu programa.

Eu definitivamente diria o primeiro.

Mike
fonte
2
+1. Além disso, como o boolnão é declarado como volatile, há a possibilidade definitiva de que as leituras subsequentes para _quitFlagno whileloop sejam otimizadas, levando a um loop infinito.
Adam Robinson,
2
Faltando a maneira recomendada de fazer isso. Eu esperava isso como uma resposta.
Iúri dos Anjos
30

Alternativamente, uma solução mais simples é apenas:

Console.ReadLine();
Cocowalla
fonte
Eu estava prestes a sugerir isso, mas não vai parar apenas em Ctrl-C
Thomas Levesque
Tive a impressão de que CTRL-C era apenas um exemplo - qualquer entrada do usuário para fechá-lo
Cocowalla
Lembre-se de que 'Console.ReadLine ()' bloqueia o thread. Portanto, o aplicativo ainda estaria em execução, mas não faria nada a não ser esperar que o usuário insira uma linha
fabriciorissetto
2
@fabriciorissetto A pergunta do OP afirma 'começar coisas assíncronas', então o aplicativo estará realizando trabalho em outro encadeamento
Cocowalla 01 de
1
@Cocowalla Eu senti falta disso. Foi mal!
fabriciorissetto
12

Você pode fazer isso (e remover o CancelKeyPressmanipulador de eventos):

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

Não tenho certeza se isso é melhor, mas não gosto da ideia de chamar Thread.Sleepem um loop .. Acho que é mais limpo bloquear a entrada do usuário.

Thomas Levesque
fonte
Não gosto que você esteja verificando as teclas Ctrl + C, em vez do sinal acionado por Ctrl + C.
CodesInChaos
9

Eu prefiro usar o Application.Run

static void Main(string[] args) {

   //Do your stuff here

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

   //Cleanup/Before Quit
}

dos documentos:

Começa a execução de um loop de mensagem de aplicativo padrão no segmento atual, sem um formulário.

Ygaradon
fonte
9
Mas então você pega uma dependência do Windows Forms apenas para isso. Não é um grande problema com a estrutura .NET tradicional, mas a tendência atual é para implantações modulares, incluindo apenas as peças de que você precisa.
CodesInChaos
4

Parece que você está tornando tudo mais difícil do que o necessário. Por que não apenas Joino tópico depois que você sinalizou para parar?

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
fonte
2
No meu caso, não controlo os threads em que o material assíncrono é executado.
intoOrbit,
1) Não gosto que você esteja verificando as teclas Ctrl + C, em vez do sinal acionado por Ctrl + C. 2) Sua abordagem não funciona se o aplicativo usa Tarefas em vez de um único thread de trabalho.
CodesInChaos
2

Também é possível bloquear o thread / programa com base em um token de cancelamento.

token.WaitHandle.WaitOne();

WaitHandle é sinalizado quando o token é cancelado.

Eu vi essa técnica usada pelo Microsoft.Azure.WebJobs.JobHost, onde o token vem de uma fonte de token de cancelamento do WebJobsShutdownWatcher (um inspetor de arquivo que encerra o trabalho).

Isso dá algum controle sobre quando o programa pode terminar.

CRice
fonte
1
Esta é uma excelente resposta para qualquer aplicativo de console do mundo real que precisa escutar um CTL+Cporque está executando uma operação de longa duração, ou é um daemon, que também deve desligar normalmente seus threads de trabalho. Você faria isso com um CancelToken e, portanto, essa resposta tira proveito de um WaitHandleque já existe, em vez de criar um novo.
mdisibio
1

Dos dois primeiros um é melhor

_quitEvent.WaitOne();

porque no segundo a thread acorda a cada milissegundo e vai para a interrupção do sistema operacional que é cara

Naveen
fonte
Esta é uma boa alternativa para Consolemétodos se você não tiver um console conectado (porque, por exemplo, o programa é iniciado por um serviço)
Marco Sulla
0

Você deve fazer isso como faria se estivesse programando um serviço do Windows. Você nunca usaria uma instrução while, em vez de usar um delegado. WaitOne () é geralmente usado enquanto espera os threads descartar - Thread.Sleep () - não é aconselhável - Você já pensou em usar System.Timers.Timer usando esse evento para verificar o evento de desligamento?

KenL
fonte