Come mantenere in esecuzione un'app per console .NET?


104

Considera un'applicazione console che avvia alcuni servizi in un thread separato. Tutto quello che deve fare è aspettare che l'utente prema Ctrl + C per spegnerlo.

Quale dei seguenti è il modo migliore per farlo?

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 questo, 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
}

Risposte:


63

si desidera sempre evitare di utilizzare i cicli while, specialmente quando si forza il codice a ricontrollare le variabili. Spreca risorse della CPU e rallenta il programma.

Direi sicuramente il primo.


2
+1. Inoltre, poiché boolnon viene dichiarato come volatile, esiste la possibilità che le letture successive _quitFlagnel whileciclo vengano ottimizzate, portando a un ciclo infinito.
Adam Robinson

2
Manca il modo consigliato per farlo. Me l'aspettavo come risposta.
Iúri dos Anjos

30

In alternativa, una soluzione più semplice è semplicemente:

Console.ReadLine();

Stavo per suggerirlo, ma non si fermerà solo su Ctrl-C
Thomas Levesque

Ho avuto l'impressione che CTRL-C fosse solo un esempio - qualsiasi input dell'utente per chiuderlo
Cocowalla

Ricorda che "Console.ReadLine ()" blocca i thread. Quindi l'applicazione sarebbe ancora in esecuzione ma non fare altro che attendere che l'utente inserisca una riga
fabriciorissetto

2
@fabriciorissetto La domanda dell'OP afferma 'kick off asynchronous stuff', quindi l'applicazione eseguirà il lavoro su un altro thread
Cocowalla

1
@ Cocowalla mi è mancato. Colpa mia!
fabriciorissetto

12

Puoi farlo (e rimuovere il CancelKeyPressgestore di eventi):

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

Non sono sicuro che sia meglio, ma non mi piace l'idea di chiamare Thread.Sleepin loop .. Penso che sia più pulito bloccare l'input dell'utente.


Non mi piace che tu stia controllando i tasti Ctrl + C, invece del segnale attivato da Ctrl + C.
CodesInChaos

9

Preferisco usare Application.Run

static void Main(string[] args) {

   //Do your stuff here

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

   //Cleanup/Before Quit
}

dai documenti:

Inizia l'esecuzione di un ciclo di messaggi dell'applicazione standard sul thread corrente, senza un modulo.


9
Ma poi prendi una dipendenza da Windows Form solo per questo. Non è un grosso problema con il tradizionale framework .NET, ma la tendenza attuale è verso distribuzioni modulari che includano solo le parti necessarie.
CodesInChaos

4

Sembra che tu lo stia rendendo più difficile del necessario. Perché non solo Joinil thread dopo che hai segnalato di fermarsi?

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);
        }
    }
}

2
Nel mio caso non controllo i thread su cui gira il materiale asincrono.
intoOrbit

1) Non mi piace che tu stia controllando i tasti Ctrl + C, invece del segnale attivato da Ctrl + C. 2) Il tuo approccio non funziona se l'applicazione utilizza Tasks invece di un singolo thread di lavoro.
CodesInChaos

2

È anche possibile bloccare il thread / programma in base a un token di annullamento.

token.WaitHandle.WaitOne();

WaitHandle viene segnalato quando il token viene annullato.

Ho visto questa tecnica utilizzata da Microsoft.Azure.WebJobs.JobHost, in cui il token proviene da un'origine del token di annullamento del WebJobsShutdownWatcher (un file watcher che termina il lavoro).

Questo dà un certo controllo su quando il programma può terminare.


1
Questa è una risposta eccellente per qualsiasi app console del mondo reale che deve ascoltare un CTL+Cperché sta eseguendo un'operazione di lunga durata, o è un daemon, che dovrebbe anche chiudere con grazia i suoi thread di lavoro. Lo faresti con un CancelToken e quindi questa risposta sfrutta un WaitHandlegià esistente, piuttosto che crearne uno nuovo.
mdisibio

1

Delle due la prima è migliore

_quitEvent.WaitOne();

perché nel secondo il thread si sveglia ogni millisecondo verrà trasformato in interrupt del sistema operativo che è costoso


Questa è una buona alternativa ai Consolemetodi se non si ha una console collegata (perché, ad esempio, il programma viene avviato da un servizio)
Marco Sulla

0

Dovresti farlo proprio come faresti se stessi programmando un servizio Windows. Non useresti mai un'istruzione while invece useresti un delegato. WaitOne () viene generalmente utilizzato in attesa che i thread vengano eliminati - Thread.Sleep () - non è consigliabile - Hai pensato di utilizzare System.Timers.Timer utilizzando quell'evento per verificare la presenza di eventi di arresto?

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.