Wie fange ich Strg-C (SIGINT) in einer C # -Konsolen-App ein?
221
Ich möchte in der Lage sein, CTRL+ Cin einer C # -Konsolenanwendung abzufangen, damit ich vor dem Beenden einige Bereinigungen durchführen kann. Was ist der beste Weg, dies zu tun?
Tatsächlich empfiehlt dieser Artikel P / Invoke und CancelKeyPresswird in den Kommentaren nur kurz erwähnt. Ein guter Artikel ist codeneverwritten.com/2006/10/…
bzlm
Dies funktioniert, aber es schließt das Schließen des Fensters mit dem X nicht ein. Siehe meine vollständige Lösung unten. funktioniert auch mit kill
JJ_Coder4Hire
1
Ich habe festgestellt, dass Console.CancelKeyPress nicht mehr funktioniert, wenn die Konsole geschlossen wird. Wenn Sie eine App unter Mono / Linux mit systemd ausführen oder wenn die App als "mono myapp.exe </ dev / null" ausgeführt wird, wird ein SIGINT an den Standard-Signalhandler gesendet und die App wird sofort beendet. Linux-Benutzer möchten möglicherweise stackoverflow.com/questions/6546509/…
@aku gibt es nicht eine gewisse Zeit, die Sie auf das SIGINT antworten müssen, bevor der unhöfliche Prozess abgebrochen wird? Ich kann es nirgendwo finden und versuche mich zu erinnern, wo ich es gelesen habe.
publicstaticvoidMain(string[] args){Console.CancelKeyPress+=delegate{// call methods to clean up};while(true){}}
Wenn der Benutzer Strg + C drückt, wird der Code im Delegaten ausgeführt und das Programm beendet. Auf diese Weise können Sie eine Bereinigung durchführen, indem Sie die erforderlichen Methoden aufrufen. Beachten Sie, dass nach der Ausführung des Delegaten kein Code mehr vorhanden ist.
Es gibt andere Situationen, in denen dies nicht funktioniert. Zum Beispiel, wenn das Programm gerade wichtige Berechnungen durchführt, die nicht sofort gestoppt werden können. In diesem Fall besteht die richtige Strategie möglicherweise darin, das Programm anzuweisen, das Programm nach Abschluss der Berechnung zu beenden. Der folgende Code gibt ein Beispiel dafür, wie dies implementiert werden kann:
classMainClass{privatestaticbool keepRunning =true;publicstaticvoidMain(string[] args){Console.CancelKeyPress+=delegate(object sender,ConsoleCancelEventArgs e){
e.Cancel=true;MainClass.keepRunning =false;};while(MainClass.keepRunning){// Do your work in here, in small chunks.// If you literally just want to wait until ctrl-c,// not doing anything, see the answer using set-reset events.}Console.WriteLine("exited gracefully");}}
Der Unterschied zwischen diesem Code und dem ersten Beispiel besteht darin, dass e.Canceltrue festgelegt ist. Dies bedeutet, dass die Ausführung nach dem Delegaten fortgesetzt wird. Wenn es ausgeführt wird, wartet das Programm darauf, dass der Benutzer Strg + C drückt. In diesem Fall keepRunningändert die Variable den Wert, wodurch die while-Schleife beendet wird. Dies ist eine Möglichkeit, das Programm ordnungsgemäß zu beenden.
keepRunningmuss möglicherweise markiert werden volatile. Der Hauptthread kann es andernfalls in einem CPU-Register zwischenspeichern und bemerkt die Wertänderung nicht, wenn der Delegat ausgeführt wird.
CDhowie
Dies funktioniert, aber es schließt das Schließen des Fensters mit dem X nicht ein. Siehe meine vollständige Lösung unten. funktioniert auch mit kill.
JJ_Coder4Hire
13
Sollte geändert werden, um a ManualResetEventanstelle von Spin auf a zu verwenden bool.
Mizipzor
Eine kleine Einschränkung für alle anderen, die laufende Inhalte in Git-Bash, MSYS2 oder CygWin ausführen: Sie müssen dotnet über winpty ausführen, damit dies funktioniert (So, winpty dotnet run). Andernfalls wird der Delegat niemals ausgeführt.
Samuel Lampa
Achtung, das Ereignis für CancelKeyPress wird in einem Thread-Pool-Thread behandelt, was nicht sofort offensichtlich ist: docs.microsoft.com/en-us/dotnet/api/…
Sam Rueby
100
Ich möchte Jonas 'Antwort ergänzen . Das Drehen auf a boolführt zu einer 100% igen CPU-Auslastung und verschwendet viel Energie, während Sie auf CTRL+ warten C.
Die bessere Lösung besteht darin, a ManualResetEventzu verwenden, um tatsächlich auf das CTRL+ zu "warten" C:
staticvoidMain(string[] args){var exitEvent =newManualResetEvent(false);Console.CancelKeyPress+=(sender, eventArgs)=>{
eventArgs.Cancel=true;
exitEvent.Set();};var server =newMyServer();// example
server.Run();
exitEvent.WaitOne();
server.Stop();}
Ich denke, der Punkt ist, dass Sie die ganze Arbeit innerhalb der while-Schleife erledigen würden und das Drücken von Strg + C nicht in der Mitte der while-Iteration unterbrochen wird. Diese Iteration wird vor dem Ausbruch abgeschlossen.
pkr298
2
@ pkr298 - Schade, dass Leute Ihren Kommentar nicht abstimmen, da er völlig wahr ist. Ich werde Jonas seine Antwort bearbeiten, um zu klären, dass die Leute nicht so denken wie Jonathon (was nicht von Natur aus schlecht ist, aber nicht so, wie Jonas seine Antwort meinte)
M. Mimpen
Aktualisieren Sie die vorhandene Antwort mit dieser Verbesserung.
Mizipzor
27
Hier ist ein vollständiges Arbeitsbeispiel. In leeres C # -Konsolenprojekt einfügen:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
namespace TestTrapCtrlC{publicclassProgram{staticbool exitSystem =false;#region Trap application termination[DllImport("Kernel32")]privatestaticexternboolSetConsoleCtrlHandler(EventHandler handler,booladd);privatedelegateboolEventHandler(CtrlType sig);staticEventHandler _handler;enumCtrlType{
CTRL_C_EVENT =0,
CTRL_BREAK_EVENT =1,
CTRL_CLOSE_EVENT =2,
CTRL_LOGOFF_EVENT =5,
CTRL_SHUTDOWN_EVENT =6}privatestaticboolHandler(CtrlType sig){Console.WriteLine("Exiting system due to external CTRL-C, or process kill, or shutdown");//do your cleanup hereThread.Sleep(5000);//simulate some cleanup delayConsole.WriteLine("Cleanup complete");//allow main to run off
exitSystem =true;//shutdown right away so there are no lingering threadsEnvironment.Exit(-1);returntrue;}#endregionstaticvoidMain(string[] args){// Some biolerplate to react to close window event, CTRL-C, kill, etc
_handler +=newEventHandler(Handler);SetConsoleCtrlHandler(_handler,true);//start your multi threaded program hereProgram p =newProgram();
p.Start();//hold the console so it doesn’t run off the endwhile(!exitSystem){Thread.Sleep(500);}}publicvoidStart(){// start a thread and start doing some processingConsole.WriteLine("Thread started, processing..");}}}
Ich habe dies nur hinzugefügt, weil es die einzige Antwort ist, die eine vollständige Antwort adressiert. Dieser ist gründlich und ermöglicht es Ihnen, das Programm unter dem Taskplaner ohne Benutzereingriff auszuführen. Sie haben immer noch die Möglichkeit aufzuräumen. Verwenden Sie NLOG in Ihrem Projekt und Sie haben etwas Verwaltbares. Ich frage mich, ob es in .NET Core 2 oder 3 kompiliert wird.
Hier ist, wie ich dieses Problem gelöst habe und wie der Benutzer sowohl das X als auch Strg-C gedrückt hat. Beachten Sie die Verwendung von ManualResetEvents. Dadurch wird der Hauptthread in den Ruhezustand versetzt, wodurch die CPU andere Threads verarbeiten kann, während sie entweder auf das Beenden oder auf die Bereinigung wartet. HINWEIS: Es ist erforderlich, das TerminationCompletedEvent am Ende von main festzulegen. Andernfalls tritt eine unnötige Latenz bei der Beendigung auf, da das Betriebssystem beim Beenden der Anwendung eine Zeitüberschreitung aufweist.
namespace CancelSample{
using System;
using System.Threading;
using System.Runtime.InteropServices;internalclassProgram{/// <summary>/// Adds or removes an application-defined HandlerRoutine function from the list of handler functions for the calling process/// </summary>/// <param name="handler">A pointer to the application-defined HandlerRoutine function to be added or removed. This parameter can be NULL.</param>/// <param name="add">If this parameter is TRUE, the handler is added; if it is FALSE, the handler is removed.</param>/// <returns>If the function succeeds, the return value is true.</returns>[DllImport("Kernel32")]privatestaticexternboolSetConsoleCtrlHandler(ConsoleCloseHandler handler,booladd);/// <summary>/// The console close handler delegate./// </summary>/// <param name="closeReason">/// The close reason./// </param>/// <returns>/// True if cleanup is complete, false to run other registered close handlers./// </returns>privatedelegateboolConsoleCloseHandler(int closeReason);/// <summary>/// Event set when the process is terminated./// </summary>privatestaticreadonlyManualResetEventTerminationRequestedEvent;/// <summary>/// Event set when the process terminates./// </summary>privatestaticreadonlyManualResetEventTerminationCompletedEvent;/// <summary>/// Static constructor/// </summary>staticProgram(){// Do this initialization here to avoid polluting Main() with it// also this is a great place to initialize multiple static// variables.TerminationRequestedEvent=newManualResetEvent(false);TerminationCompletedEvent=newManualResetEvent(false);SetConsoleCtrlHandler(OnConsoleCloseEvent,true);}/// <summary>/// The main console entry point./// </summary>/// <param name="args">The commandline arguments.</param>privatestaticvoidMain(string[] args){// Wait for the termination eventwhile(!TerminationRequestedEvent.WaitOne(0)){// Something to do while waitingConsole.WriteLine("Work");}// Sleep until terminationTerminationRequestedEvent.WaitOne();// Print a message which represents the operationConsole.WriteLine("Cleanup");// Set this to terminate immediately (if not set, the OS will// eventually kill the process)TerminationCompletedEvent.Set();}/// <summary>/// Method called when the user presses Ctrl-C/// </summary>/// <param name="reason">The close reason</param>privatestaticboolOnConsoleCloseEvent(int reason){// Signal terminationTerminationRequestedEvent.Set();// Wait for cleanupTerminationCompletedEvent.WaitOne();// Don't run other handlers, just exit.returntrue;}}}
Dies kann dazu führen, dass für ReadLine zwei Eingabetasten für jede Eingabe erforderlich sind.
Grault
0
Ich kann vor dem Beenden einige Aufräumarbeiten durchführen. Was ist der beste Weg, dies zu tun? Das ist das eigentliche Ziel: Trap Exit, um deine eigenen Sachen zu machen. Und weitere Antworten oben machen es nicht richtig. Weil Strg + C nur eine von vielen Möglichkeiten ist, die App zu beenden.
Was in dotnet c # dafür benötigt wird - sogenanntes Stornierungs-Token, das an Host.RunAsync(ct)Windows übergeben wird, und dann in Exit-Signal-Traps für Windows
CancelKeyPress
wird in den Kommentaren nur kurz erwähnt. Ein guter Artikel ist codeneverwritten.com/2006/10/…Das Console.CancelKeyPress Ereignis wird dafür verwendet. So wird es verwendet:
Wenn der Benutzer Strg + C drückt, wird der Code im Delegaten ausgeführt und das Programm beendet. Auf diese Weise können Sie eine Bereinigung durchführen, indem Sie die erforderlichen Methoden aufrufen. Beachten Sie, dass nach der Ausführung des Delegaten kein Code mehr vorhanden ist.
Es gibt andere Situationen, in denen dies nicht funktioniert. Zum Beispiel, wenn das Programm gerade wichtige Berechnungen durchführt, die nicht sofort gestoppt werden können. In diesem Fall besteht die richtige Strategie möglicherweise darin, das Programm anzuweisen, das Programm nach Abschluss der Berechnung zu beenden. Der folgende Code gibt ein Beispiel dafür, wie dies implementiert werden kann:
Der Unterschied zwischen diesem Code und dem ersten Beispiel besteht darin, dass
e.Cancel
true festgelegt ist. Dies bedeutet, dass die Ausführung nach dem Delegaten fortgesetzt wird. Wenn es ausgeführt wird, wartet das Programm darauf, dass der Benutzer Strg + C drückt. In diesem FallkeepRunning
ändert die Variable den Wert, wodurch die while-Schleife beendet wird. Dies ist eine Möglichkeit, das Programm ordnungsgemäß zu beenden.quelle
keepRunning
muss möglicherweise markiert werdenvolatile
. Der Hauptthread kann es andernfalls in einem CPU-Register zwischenspeichern und bemerkt die Wertänderung nicht, wenn der Delegat ausgeführt wird.ManualResetEvent
anstelle von Spin auf a zu verwendenbool
.winpty dotnet run
). Andernfalls wird der Delegat niemals ausgeführt.Ich möchte Jonas 'Antwort ergänzen . Das Drehen auf a
bool
führt zu einer 100% igen CPU-Auslastung und verschwendet viel Energie, während Sie auf CTRL+ warten C.Die bessere Lösung besteht darin, a
ManualResetEvent
zu verwenden, um tatsächlich auf das CTRL+ zu "warten" C:quelle
Hier ist ein vollständiges Arbeitsbeispiel. In leeres C # -Konsolenprojekt einfügen:
quelle
Diese Frage ist sehr ähnlich zu:
Capture Console Exit C #
Hier ist, wie ich dieses Problem gelöst habe und wie der Benutzer sowohl das X als auch Strg-C gedrückt hat. Beachten Sie die Verwendung von ManualResetEvents. Dadurch wird der Hauptthread in den Ruhezustand versetzt, wodurch die CPU andere Threads verarbeiten kann, während sie entweder auf das Beenden oder auf die Bereinigung wartet. HINWEIS: Es ist erforderlich, das TerminationCompletedEvent am Ende von main festzulegen. Andernfalls tritt eine unnötige Latenz bei der Beendigung auf, da das Betriebssystem beim Beenden der Anwendung eine Zeitüberschreitung aufweist.
quelle
Console.TreatControlCAsInput = true;
hat für mich gearbeitet.quelle
Was in dotnet c # dafür benötigt wird - sogenanntes Stornierungs-Token, das an
Host.RunAsync(ct)
Windows übergeben wird, und dann in Exit-Signal-Traps für Windows...
quelle