Me gustaría poder atrapar CTRL+ Cen una aplicación de consola C # para poder realizar algunas limpiezas antes de salir. Cual es la mejor manera de hacer esto?
Me gustaría poder atrapar CTRL+ Cen una aplicación de consola C # para poder realizar algunas limpiezas antes de salir. Cual es la mejor manera de hacer esto?
Respuestas:
Ver MSDN:
Artículo con ejemplos de código:
El evento Console.CancelKeyPress se utiliza para esto. Así es como se usa:
public static void Main(string[] args)
{
Console.CancelKeyPress += delegate {
// call methods to clean up
};
while (true) {}
}
Cuando el usuario presiona Ctrl + C, se ejecuta el código en el delegado y se cierra el programa. Esto le permite realizar la limpieza llamando a los métodos necesarios. Tenga en cuenta que no hay código después de que se ejecuta el delegado.
Hay otras situaciones en las que esto no es suficiente. Por ejemplo, si el programa está realizando cálculos importantes que no se pueden detener de inmediato. En ese caso, la estrategia correcta podría ser decirle al programa que salga después de que se complete el cálculo. El siguiente código da un ejemplo de cómo se puede implementar esto:
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 diferencia entre este código y el primer ejemplo es que e.Cancel
se establece en verdadero, lo que significa que la ejecución continúa después del delegado. Si se ejecuta, el programa espera a que el usuario presione Ctrl + C. Cuando eso sucede, la keepRunning
variable cambia el valor que hace que el ciclo while salga. Esta es una manera de hacer que el programa salga con gracia.
keepRunning
puede necesitar ser marcado volatile
. De lo contrario, el hilo principal podría almacenarlo en caché en un registro de CPU y no notará el cambio de valor cuando se ejecute el delegado.
ManualResetEvent
lugar de girar en un bool
.
winpty dotnet run
). De lo contrario, el delegado nunca se ejecutará.
Me gustaría agregar a la respuesta de Jonas . Girar sobre un bool
causará un 100% de utilización de la CPU y desperdiciará un montón de energía sin hacer nada mientras espera CTRL+ C.
La mejor solución es usar a ManualResetEvent
para "esperar" realmente el 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();
}
Aquí hay un ejemplo de trabajo completo. pegar en proyecto de consola C # vacío:
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..");
}
}
}
Esta pregunta es muy similar a:
Capture la salida de la consola C #
Así es como resolví este problema y traté con el usuario presionando la X y Ctrl-C. Observe el uso de ManualResetEvents. Esto hará que el subproceso principal se suspenda, lo que libera a la CPU para procesar otros subprocesos mientras espera la salida o la limpieza. NOTA: es necesario establecer TerminationCompletedEvent al final de main. De lo contrario, se produce una latencia innecesaria en la finalización debido al tiempo de espera del sistema operativo mientras se cierra la aplicación.
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;
}
}
}
Puedo realizar algunas limpiezas antes de salir. ¿Cuál es la mejor manera de hacer esto? Ese es el verdadero objetivo: salir de la trampa, hacer tus propias cosas. Y otras respuestas anteriores no lo hacen bien. Porque, Ctrl + C es solo una de las muchas formas de salir de la aplicación.
Lo que en dotnet C # es necesaria para que - por lo que llamó la cancelación símbolo pasó a Host.RunAsync(ct)
y, a continuación, en las trampas de las señales de salida, para Windows sería
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
solo se menciona brevemente en los comentarios. Un buen artículo es codeneverwritten.com/2006/10/…