Wie kann eine .NET-Konsolen-App ausgeführt werden?

104

Stellen Sie sich eine Konsolenanwendung vor, die einige Dienste in einem separaten Thread startet. Sie müssen lediglich warten, bis der Benutzer Strg + C drückt, um das System herunterzufahren.

Welcher der folgenden Wege ist der bessere?

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
}

Oder dies mit 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
}
inOrbit
quelle

Antworten:

63

Sie möchten immer die Verwendung von while-Schleifen verhindern, insbesondere wenn Sie den Code zwingen, Variablen erneut zu überprüfen. Es verschwendet CPU-Ressourcen und verlangsamt Ihr Programm.

Ich würde definitiv den ersten sagen.

Mike
quelle
2
+1. Da das boolnicht als deklariert ist volatile, besteht außerdem die eindeutige Möglichkeit, dass nachfolgende Lesevorgänge _quitFlagin der whileSchleife wegoptimiert werden, was zu einer Endlosschleife führt.
Adam Robinson
2
Fehlt der empfohlene Weg, dies zu tun. Ich hatte dies als Antwort erwartet.
Iúri dos Anjos
30

Alternativ ist eine einfachere Lösung einfach:

Console.ReadLine();
Cocowalla
quelle
Ich wollte das vorschlagen, aber es wird nicht nur auf Strg-C
Thomas Levesque
Ich hatte den Eindruck, dass STRG-C nur ein Beispiel war - jede Benutzereingabe, um es zu schließen
Cocowalla
Denken Sie daran, dass 'Console.ReadLine ()' Thread blockiert. Die Anwendung würde also immer noch ausgeführt, aber nichts anderes tun, als darauf zu warten, dass der Benutzer eine Zeile
eingibt
2
@fabriciorissetto Die OP-Frage lautet "asynchrones Zeug starten", sodass die Anwendung Arbeiten an einem anderen Thread
ausführt
1
@Cocowalla Das habe ich verpasst. Mein Fehler!
Fabriciorissetto
12

Sie können dies tun (und den CancelKeyPressEreignishandler entfernen ):

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

Ich bin mir nicht sicher, ob das besser ist, aber ich mag die Idee, Thread.Sleepeine Schleife aufzurufen , nicht. Ich denke, es ist sauberer, Benutzereingaben zu blockieren.

Thomas Levesque
quelle
Ich mag es nicht, dass Sie nach den Tasten Strg + C suchen, anstatt nach dem Signal, das durch Strg + C ausgelöst wird.
CodesInChaos
9

Ich bevorzuge die Verwendung der Application.Run

static void Main(string[] args) {

   //Do your stuff here

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

   //Cleanup/Before Quit
}

aus den Dokumenten:

Startet die Ausführung einer Standardanwendungsnachrichtenschleife für den aktuellen Thread ohne Formular.

Ygaradon
quelle
9
Aber dann nehmen Sie nur dafür eine Abhängigkeit von Windows-Formularen. Kein allzu großes Problem mit dem traditionellen .NET-Framework, aber der aktuelle Trend geht zu modularen Bereitstellungen, die nur die Teile enthalten, die Sie benötigen.
CodesInChaos
4

Scheint, als würdest du es schwieriger machen als nötig. Warum nicht einfach Joinden Thread, nachdem Sie ihm signalisiert haben, dass er aufhören soll?

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
quelle
2
In meinem Fall kontrolliere ich nicht die Threads, auf denen das asynchrone Material ausgeführt wird.
Orbit
1) Ich mag es nicht, dass Sie nach den Tasten Strg + C suchen, anstatt nach dem Signal, das durch Strg + C ausgelöst wird. 2) Ihr Ansatz funktioniert nicht, wenn die Anwendung Aufgaben anstelle eines einzelnen Arbeitsthreads verwendet.
CodesInChaos
2

Es ist auch möglich, den Thread / das Programm basierend auf einem Abbruch-Token zu blockieren.

token.WaitHandle.WaitOne();

WaitHandle wird signalisiert, wenn das Token abgebrochen wird.

Ich habe diese Technik gesehen, die von Microsoft.Azure.WebJobs.JobHost verwendet wird, wobei das Token von einer Storno-Token-Quelle des WebJobsShutdownWatcher (einem Datei-Watcher, der den Job beendet) stammt.

Dies gibt eine gewisse Kontrolle darüber, wann das Programm enden kann.

CRice
quelle
1
Dies ist eine hervorragende Antwort für jede reale Konsolen-App, die auf a warten muss, CTL+Cweil sie einen lang laufenden Vorgang ausführt oder ein Daemon ist, der auch seine Arbeitsthreads ordnungsgemäß herunterfahren sollte. Sie würden dies mit einem CancelToken tun, und daher nutzt diese Antwort eine WaitHandlebereits vorhandene Antwort , anstatt eine neue zu erstellen.
mdisibio
1

Von den beiden ist der erste besser

_quitEvent.WaitOne();

denn im zweiten wird der Thread jede Millisekunde aufgeweckt und in einen teuren Betriebssystem-Interrupt umgewandelt

Naveen
quelle
Dies ist eine gute Alternative für ConsoleMethoden, wenn Sie keine Konsole angeschlossen haben (weil das Programm beispielsweise von einem Dienst gestartet wird)
Marco Sulla
0

Sie sollten es genauso machen, als würden Sie einen Windows-Dienst programmieren. Sie würden niemals eine while-Anweisung verwenden, sondern einen Delegaten. WaitOne () wird im Allgemeinen verwendet, während auf das Entsorgen von Threads gewartet wird - Thread.Sleep () - ist nicht ratsam - Haben Sie darüber nachgedacht, System.Timers.Timer zu verwenden, um dieses Ereignis zum Überprüfen des Herunterfahrereignisses zu verwenden?

KenL
quelle