Vorrei essere in grado di intercettare CTRL+ Cin un'applicazione console C # in modo da poter eseguire alcune pulizie prima di uscire. Qual è il modo migliore per fare questo?
Vorrei essere in grado di intercettare CTRL+ Cin un'applicazione console C # in modo da poter eseguire alcune pulizie prima di uscire. Qual è il modo migliore per fare questo?
Risposte:
Vedi MSDN:
Articolo con esempi di codice:
Il Console.CancelKeyPress evento è usato per questo. Ecco come viene utilizzato:
public static void Main(string[] args)
{
Console.CancelKeyPress += delegate {
// call methods to clean up
};
while (true) {}
}
Quando l'utente preme Ctrl + C, il codice nel delegato viene eseguito e il programma termina. Ciò consente di eseguire la pulizia chiamando i metodi necessari. Si noti che nessun codice dopo l'esecuzione del delegato.
Ci sono altre situazioni in cui questo non lo taglierà. Ad esempio, se il programma sta attualmente eseguendo calcoli importanti che non possono essere immediatamente arrestati. In tal caso, la strategia corretta potrebbe essere quella di dire al programma di uscire dopo il completamento del calcolo. Il codice seguente fornisce un esempio di come implementarlo:
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");
}
}
La differenza tra questo codice e il primo esempio è che e.Cancel
è impostato su true, il che significa che l'esecuzione continua dopo il delegato. Se eseguito, il programma attende che l'utente prema Ctrl + C. Quando ciò accade, la keepRunning
variabile cambia valore che provoca la chiusura del ciclo while. Questo è un modo per far uscire il programma con garbo.
keepRunning
potrebbe essere necessario essere contrassegnati volatile
. In caso contrario, il thread principale potrebbe memorizzarlo nella cache su un registro CPU e non noterà la modifica del valore quando viene eseguito il delegato.
ManualResetEvent
anziché spin su a bool
.
winpty dotnet run
). Altrimenti, il delegato non verrà mai eseguito.
Vorrei aggiungere la risposta di Jonas . Girare su una bool
causa il 100% di utilizzo della CPU e sprecherà un sacco di energia senza fare molto durante l'attesa di CTRL+ C.
La soluzione migliore è utilizzare a ManualResetEvent
per "attendere" effettivamente il CTRL+ 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();
}
Ecco un esempio di lavoro completo. incolla nel progetto console C # vuoto:
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..");
}
}
}
Questa domanda è molto simile a:
Ecco come ho risolto questo problema e ho affrontato l'utente colpendo la X e Ctrl-C. Si noti l'uso di ManualResetEvents. Ciò causerà la sospensione del thread principale che libera la CPU per elaborare altri thread in attesa dell'uscita o della pulizia. NOTA: è necessario impostare TerminationCompletedEvent alla fine di main. In caso contrario, si causa latenza non necessaria nella risoluzione a causa del timeout del sistema operativo durante l'uccisione dell'applicazione.
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;
}
}
}
Posso effettuare alcune pulizie prima di uscire. Qual è il modo migliore per farlo Questo è il vero obiettivo: uscire dalla trappola, creare le tue cose. E le altre risposte sopra non lo rendono giusto. Perché, Ctrl + C è solo uno dei molti modi per uscire dall'app.
Ciò che in dotnet c # è necessario per questo - il cosiddetto token di annullamento passato a Host.RunAsync(ct)
e quindi, nelle trappole dei segnali di uscita, per Windows sarebbe
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
è menzionato solo brevemente nei commenti. Un buon articolo è codeneverwritten.com/2006/10/…