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?
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?
Antworten:
Das Console.CancelKeyPress Ereignis wird dafür verwendet. So wird es verwendet:
public static void Main(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:
class MainClass
{
private static bool keepRunning = true;
public static void Main(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.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 Fall keepRunning
ändert die Variable den Wert, wodurch die while-Schleife beendet wird. Dies ist eine Möglichkeit, das Programm ordnungsgemäß zu beenden.
keepRunning
muss 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.
ManualResetEvent
anstelle von Spin auf a zu verwenden bool
.
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:
static void Main(string[] args) {
var exitEvent = new ManualResetEvent(false);
Console.CancelKeyPress += (sender, eventArgs) => {
eventArgs.Cancel = true;
exitEvent.Set();
};
var server = new MyServer(); // example
server.Run();
exitEvent.WaitOne();
server.Stop();
}
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 {
public class Program {
static bool exitSystem = false;
#region Trap application termination
[DllImport("Kernel32")]
private static extern bool SetConsoleCtrlHandler(EventHandler handler, bool add);
private delegate bool EventHandler(CtrlType sig);
static EventHandler _handler;
enum CtrlType {
CTRL_C_EVENT = 0,
CTRL_BREAK_EVENT = 1,
CTRL_CLOSE_EVENT = 2,
CTRL_LOGOFF_EVENT = 5,
CTRL_SHUTDOWN_EVENT = 6
}
private static bool Handler(CtrlType sig) {
Console.WriteLine("Exiting system due to external CTRL-C, or process kill, or shutdown");
//do your cleanup here
Thread.Sleep(5000); //simulate some cleanup delay
Console.WriteLine("Cleanup complete");
//allow main to run off
exitSystem = true;
//shutdown right away so there are no lingering threads
Environment.Exit(-1);
return true;
}
#endregion
static void Main(string[] args) {
// Some biolerplate to react to close window event, CTRL-C, kill, etc
_handler += new EventHandler(Handler);
SetConsoleCtrlHandler(_handler, true);
//start your multi threaded program here
Program p = new Program();
p.Start();
//hold the console so it doesn’t run off the end
while (!exitSystem) {
Thread.Sleep(500);
}
}
public void Start() {
// start a thread and start doing some processing
Console.WriteLine("Thread started, processing..");
}
}
}
Diese Frage ist sehr ähnlich zu:
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;
internal class Program
{
/// <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")]
private static extern bool SetConsoleCtrlHandler(ConsoleCloseHandler handler, bool add);
/// <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>
private delegate bool ConsoleCloseHandler(int closeReason);
/// <summary>
/// Event set when the process is terminated.
/// </summary>
private static readonly ManualResetEvent TerminationRequestedEvent;
/// <summary>
/// Event set when the process terminates.
/// </summary>
private static readonly ManualResetEvent TerminationCompletedEvent;
/// <summary>
/// Static constructor
/// </summary>
static Program()
{
// Do this initialization here to avoid polluting Main() with it
// also this is a great place to initialize multiple static
// variables.
TerminationRequestedEvent = new ManualResetEvent(false);
TerminationCompletedEvent = new ManualResetEvent(false);
SetConsoleCtrlHandler(OnConsoleCloseEvent, true);
}
/// <summary>
/// The main console entry point.
/// </summary>
/// <param name="args">The commandline arguments.</param>
private static void Main(string[] args)
{
// Wait for the termination event
while (!TerminationRequestedEvent.WaitOne(0))
{
// Something to do while waiting
Console.WriteLine("Work");
}
// Sleep until termination
TerminationRequestedEvent.WaitOne();
// Print a message which represents the operation
Console.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>
private static bool OnConsoleCloseEvent(int reason)
{
// Signal termination
TerminationRequestedEvent.Set();
// Wait for cleanup
TerminationCompletedEvent.WaitOne();
// Don't run other handlers, just exit.
return true;
}
}
}
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
private static readonly CancellationTokenSource cts = new CancellationTokenSource();
public static int Main(string[] args)
{
// For gracefull shutdown, trap unload event
AppDomain.CurrentDomain.ProcessExit += (sender, e) =>
{
cts.Cancel();
exitEvent.Wait();
};
Console.CancelKeyPress += (sender, e) =>
{
cts.Cancel();
exitEvent.Wait();
};
host.RunAsync(cts);
Console.WriteLine("Shutting down");
exitEvent.Set();
return 0;
}
...
CancelKeyPress
wird in den Kommentaren nur kurz erwähnt. Ein guter Artikel ist codeneverwritten.com/2006/10/…