Evento de saída do aplicativo do console .NET

No .net, existe um método, como um evento, para detectar quando um aplicativo de console está saindo? Eu preciso limpar alguns tópicos e objects COM.

Estou executando um loop de mensagens, sem um formulário, do aplicativo de console. Um componente DCOM que estou usando parece exigir que o aplicativo bombear mensagens.

Eu tentei adicionar um manipulador para Process.GetCurrentProcess.Exited e Process.GetCurrentProcess.Disposed.

Eu também tentei adicionar um manipulador para Application.ApplicationExit e Application.ThreadExit events, mas eles não estão triggersndo. Talvez seja porque não estou usando um formulário.

    Você pode usar o evento ProcessExit do 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"); } } 

    Atualizar

    Aqui está um programa de exemplo completo com uma “bomba de mensagem” vazia em execução em um thread separado, que permite ao usuário inserir um comando de parada no console para encerrar o aplicativo normalmente. Após o loop no MessagePump, você provavelmente desejará limpar os resources usados ​​pelo thread de uma maneira agradável. É melhor fazer isso do que no ProcessExit por vários motivos:

    • Evite problemas de cross-threading; Se objects COM externos foram criados no encadeamento MessagePump, é mais fácil lidar com eles lá.
    • Há um limite de tempo no ProcessExit (3 segundos por padrão), portanto, se a limpeza for demorada, poderá falhar se o pefromed estiver dentro desse manipulador de events.

    Aqui está o 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"); } } 

    Aqui está uma solução .Net completa e muito simples que funciona em todas as versões do Windows. Basta colá-lo em um novo projeto, executá-lo e tentar CTRL-C para ver como ele lida com ele:

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

    O aplicativo é um servidor que simplesmente é executado até que o sistema seja desligado ou receba um Ctrl + C ou a janela do console seja fechada.

    Devido à natureza extraordinária da aplicação, não é viável sair “graciosamente”. (Pode ser que eu possa codificar outro aplicativo que enviaria uma mensagem de “desligamento do servidor”, mas isso seria um exagero para um aplicativo e ainda insuficiente para determinadas circunstâncias, como quando o servidor (sistema operacional real) está sendo encerrado.)

    Por causa dessas circunstâncias eu adicionei um ” ConsoleCtrlHandler ” onde eu paro meus threads e limpo meus objects 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 configuração parece funcionar perfeitamente. Aqui está um link para algum código C # para a mesma coisa.

    Para o caso CTRL + C, você pode usar isto:

     // 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); } 

    Se você estiver usando um aplicativo de console e estiver bombeando mensagens, não poderá usar a mensagem WM_QUIT?

    Como um bom exemplo pode valer a pena navegar para este projeto e ver como lidar com a saída dos processos gramaticalmente ou neste snippet da VM encontrado aqui

      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; }