Evento de salida de la aplicación .NET Console

En .NET, ¿hay algún método, como un evento, para detectar cuándo sale una aplicación de consola? Necesito limpiar algunos hilos y objetos COM.

Estoy ejecutando un bucle de mensajes, sin un formulario, desde la aplicación de la consola. Parece que un componente DCOM que estoy usando requiere que la aplicación envíe mensajes.

He intentado agregar un controlador a Process.GetCurrentProcess.Exited y Process.GetCurrentProcess.Disposed.

También intenté agregar un controlador a los eventos Application.ApplicationExit y Application.ThreadExit, pero no están activando. Quizás eso se deba a que no estoy usando un formulario.

Puede usar el evento ProcessExit del AppDomain :

 class Program { static void Main(string[] args) { AppDomain.CurrentDomain.ProcessExit += new EventHandler(CurrentDomain_ProcessExit); // do some work } static void CurrentDomain_ProcessExit(object sender, EventArgs e) { Console.WriteLine("exit"); } } 

Actualizar

Aquí hay un progtwig de ejemplo completo con una “bomba de mensajes” vacía que se ejecuta en un hilo separado, que le permite al usuario ingresar un comando de abandono en la consola para cerrar la aplicación correctamente. Después del bucle en MessagePump, probablemente querrá limpiar los recursos utilizados por el hilo de una manera agradable. Es mejor hacer eso allí que en ProcessExit por varias razones:

  • Evite problemas de cruce de hilos; si se crearon objetos COM externos en el hilo MessagePump, es más fácil tratarlos allí.
  • Hay un límite de tiempo en ProcessExit (3 segundos por defecto), por lo que si la limpieza consume mucho tiempo, puede fallar si se procesa dentro de ese controlador de eventos.

Aquí está el código:

 class Program { private static bool _quitRequested = false; private static object _syncLock = new object(); private static AutoResetEvent _waitHandle = new AutoResetEvent(false); static void Main(string[] args) { AppDomain.CurrentDomain.ProcessExit += new EventHandler(CurrentDomain_ProcessExit); // start the message pumping thread Thread msgThread = new Thread(MessagePump); msgThread.Start(); // read input to detect "quit" command string command = string.Empty; do { command = Console.ReadLine(); } while (!command.Equals("quit", StringComparison.InvariantCultureIgnoreCase)); // signal that we want to quit SetQuitRequested(); // wait until the message pump says it's done _waitHandle.WaitOne(); // perform any additional cleanup, logging or whatever } private static void SetQuitRequested() { lock (_syncLock) { _quitRequested = true; } } private static void MessagePump() { do { // act on messages } while (!_quitRequested); _waitHandle.Set(); } static void CurrentDomain_ProcessExit(object sender, EventArgs e) { Console.WriteLine("exit"); } } 

Aquí hay una solución .Net completa y muy simple que funciona en todas las versiones de Windows. Simplemente péguelo en un nuevo proyecto, ejecútelo y pruebe CTRL-C para ver cómo lo maneja:

 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.."); } } } 

La aplicación es un servidor que simplemente se ejecuta hasta que el sistema se apaga o recibe una Ctrl + C o la ventana de la consola se cierra.

Debido a la naturaleza extraordinaria de la aplicación, no es posible salir “con gracia”. (Puede ser que yo pueda codificar otra aplicación que enviaría un mensaje de “apagado del servidor”, pero eso sería excesivo para una aplicación y aún insuficiente para ciertas circunstancias, como cuando el servidor (sistema operativo real) se está cerrando realmente).

Debido a estas circunstancias, agregué un ” ConsoleCtrlHandler ” donde detengo mis hilos y limpio mis objetos COM, etc.

 Public Declare Auto Function SetConsoleCtrlHandler Lib "kernel32.dll" (ByVal Handler As HandlerRoutine, ByVal Add As Boolean) As Boolean Public Delegate Function HandlerRoutine(ByVal CtrlType As CtrlTypes) As Boolean Public Enum CtrlTypes CTRL_C_EVENT = 0 CTRL_BREAK_EVENT CTRL_CLOSE_EVENT CTRL_LOGOFF_EVENT = 5 CTRL_SHUTDOWN_EVENT End Enum Public Function ControlHandler(ByVal ctrlType As CtrlTypes) As Boolean . .clean up code here . End Function Public Sub Main() . . . SetConsoleCtrlHandler(New HandlerRoutine(AddressOf ControlHandler), True) . . End Sub 

Esta configuración parece funcionar perfectamente. Aquí hay un enlace a un código de C # para la misma cosa.

Para el caso CTRL + C, puede usar esto:

 // Tell the system console to handle CTRL+C by calling our method that // gracefully shuts down. Console.CancelKeyPress += new ConsoleCancelEventHandler(Console_CancelKeyPress); static void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs e) { Console.WriteLine("Shutting down..."); // Cleanup here System.Threading.Thread.Sleep(750); } 

Si está utilizando una aplicación de consola y está transfiriendo mensajes, ¿no puede usar el mensaje WM_QUIT?

Como un buen ejemplo, puede valer la pena navegar a este proyecto y ver cómo manejar los procesos de salida gtwigticalmente o en este fragmento de VM que se encuentra aquí.

  ConsoleOutputStream = new ObservableCollection(); var startInfo = new ProcessStartInfo(FilePath) { WorkingDirectory = RootFolderPath, Arguments = StartingArguments, RedirectStandardOutput = true, UseShellExecute = false, CreateNoWindow = true }; ConsoleProcess = new Process {StartInfo = startInfo}; ConsoleProcess.EnableRaisingEvents = true; ConsoleProcess.OutputDataReceived += (sender, args) => { App.Current.Dispatcher.Invoke((System.Action) delegate { ConsoleOutputStream.Insert(0, args.Data); //ConsoleOutputStream.Add(args.Data); }); }; ConsoleProcess.Exited += (sender, args) => { InProgress = false; }; ConsoleProcess.Start(); ConsoleProcess.BeginOutputReadLine(); } } private void RegisterProcessWatcher() { startWatch = new ManagementEventWatcher( new WqlEventQuery($"SELECT * FROM Win32_ProcessStartTrace where ProcessName = '{FileName}'")); startWatch.EventArrived += new EventArrivedEventHandler(startProcessWatch_EventArrived); stopWatch = new ManagementEventWatcher( new WqlEventQuery($"SELECT * FROM Win32_ProcessStopTrace where ProcessName = '{FileName}'")); stopWatch.EventArrived += new EventArrivedEventHandler(stopProcessWatch_EventArrived); } private void stopProcessWatch_EventArrived(object sender, EventArrivedEventArgs e) { InProgress = false; } private void startProcessWatch_EventArrived(object sender, EventArrivedEventArgs e) { InProgress = true; }