Capturar saída do console C #

92

Tenho um aplicativo de console que contém muitos threads. Existem threads que monitoram certas condições e encerram o programa se forem verdadeiras. Essa rescisão pode acontecer a qualquer momento.

Preciso de um evento que possa ser disparado quando o programa estiver fechando para que eu possa limpar todos os outros threads e fechar todos os identificadores de arquivo e conexões corretamente. Não tenho certeza se já existe um integrado ao .NET framework, então estou perguntando antes de escrever o meu.

Eu estava me perguntando se houve um evento nos moldes de:

MyConsoleProgram.OnExit += CleanupBeforeExit;
ZeroKelvin
fonte
2
Eu sei que este é um comentário muito atrasado, mas você realmente não precisa fazer isso se "fechar arquivos e conexões" for a única coisa que você deseja fazer como limpeza. Porque o Windows já fecha todos os identificadores associados a um processo durante o encerramento.
Sedat Kapanoglu
6
^ Somente se esses recursos pertencerem ao processo que está sendo encerrado. Isso é absolutamente necessário se, por exemplo, você está automatizando um aplicativo COM oculto (digamos, Word ou Excel) em segundo plano e precisa ter certeza de
eliminá-
1
esta tem uma resposta curta stackoverflow.com/questions/2555292/…
barlop

Respostas:

96

Não tenho certeza de onde encontrei o código na web, mas agora o encontrei em um de meus projetos antigos. Isso permitirá que você faça um código de limpeza em seu console, por exemplo, quando for fechado abruptamente ou devido a um desligamento ...

[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)
{
  switch (sig)
  {
      case CtrlType.CTRL_C_EVENT:
      case CtrlType.CTRL_LOGOFF_EVENT:
      case CtrlType.CTRL_SHUTDOWN_EVENT:
      case CtrlType.CTRL_CLOSE_EVENT:
      default:
          return false;
  }
}


static void Main(string[] args)
{
  // Some biolerplate to react to close window event
  _handler += new EventHandler(Handler);
  SetConsoleCtrlHandler(_handler, true);
  ...
}

Atualizar

Para quem não está verificando os comentários, parece que essa solução em particular não funciona bem (ou nem funciona) no Windows 7 . O seguinte tópico fala sobre isso

flq
fonte
4
Você pode usar isso para cancelar a saída? Exceto para quando estiver desligando!
ingh.am
7
Isso funciona muito bem, apenas bool Handler()deve return false;(não retorna nada no código) para que funcione. Se retornar verdadeiro, o Windows exibe a caixa de diálogo "Encerrar processo agora". = D
Cipi
3
Parece que esta solução não funciona com o Windows 7 para evento de desligamento, consulte social.msdn.microsoft.com/Forums/en/windowscompatibility/thread/…
CharlesB
3
Esteja ciente de que se você colocar um ponto de interrupção no método 'Handler', ele lançará uma NullReferenceException. Verificado no VS2010, Windows 7.
Máximo
10
Isso funcionou muito bem para mim no Windows 7 (64 bits). Não sei por que todo mundo está dizendo que não. As únicas modificações importantes que fiz foi livrar-me do enum e da instrução switch e "retornar false" do método - eu faço toda a minha limpeza no corpo do método.
BrainSlugs83 de
25

Exemplo completo de funcionamento, funciona com ctrl-c, fechando as janelas com X e kill:

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 boilerplate 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..");
        }
    }
}
JJ_Coder4Hire
fonte
2
Eu testei isso no Windows 7 com tudo comentado, Handlerexceto para o return truee um loop while para contar os segundos. O aplicativo continua a ser executado em ctrl-c, mas fecha após 5 segundos ao fechar com o X.
Antonios Hadjigeorgalis
Lamento, mas usando este código, só consigo obter "Limpeza concluída" se pressionar Ctrl + C, não se fechar com o botão 'X'; no último caso, recebo apenas "Saindo do sistema devido a CTRL-C externo, ouHandler eliminação de processo ou desligamento", mas parece que o console fecha antes de executar a parte restante do método {using Win10, .NET Framework 4.6.1}
Giacomo Pirinoli
8

Verifique também:

AppDomain.CurrentDomain.ProcessExit
Jmservera
fonte
7
Isso só aparece para capturar as saídas de return ou Environment.Exit, não captura CTRL + C, CTRL + Break, nem o botão Fechar real no console.
Kit10
Se você lidar com CTRL + C separadamente usando Console.CancelKeyPressseguida ProcessExitevento realmente levantou afinal CancelKeyPressmanipuladores de eventos de execução.
Konard
5

Eu tive um problema semelhante, apenas meu aplicativo de console estaria executando em um loop infinito com uma instrução preemptiva no meio. Aqui está minha solução:

class Program
{
    static int Main(string[] args)
    {
        // Init Code...
        Console.CancelKeyPress += Console_CancelKeyPress;  // Register the function to cancel event

        // I do my stuffs

        while ( true )
        {
            // Code ....
            SomePreemptiveCall();  // The loop stucks here wating function to return
            // Code ...
        }
        return 0;  // Never comes here, but...
    }

    static void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs e)
    {
        Console.WriteLine("Exiting");
        // Termitate what I have to terminate
        Environment.Exit(-1);
    }
}
João portela
fonte
4

Parece que os threads estão encerrando o aplicativo diretamente? Talvez seja melhor ter um thread sinalizando o thread principal para dizer que o aplicativo deve ser encerrado.

Ao receber este sinal, o encadeamento principal pode encerrar de forma limpa os outros encadeamentos e, finalmente, fechar-se.

Roubar
fonte
3
Eu tenho que concordar com esta resposta. Forçar a saída do aplicativo e tentar limpá-lo depois não é o caminho certo. Controle seu aplicativo, Noit. Não deixe que ele controle você.
Randolpho
1
Um thread gerado por mim diretamente não é necessariamente a única coisa que pode fechar meu aplicativo. Ctrl-C e o "botão Fechar" são outras maneiras de encerrar. O código postado por Frank, após pequenas modificações, se encaixa perfeitamente.
ZeroKelvin
4

A resposta de ZeroKelvin funciona no aplicativo de console do Windows 10 x64, .NET 4.6. Para aqueles que não precisam lidar com o enum CtrlType, aqui está uma maneira realmente simples de se conectar ao desligamento do framework:

class Program
{
    private delegate bool ConsoleCtrlHandlerDelegate(int sig);

    [DllImport("Kernel32")]
    private static extern bool SetConsoleCtrlHandler(ConsoleCtrlHandlerDelegate handler, bool add);

    static ConsoleCtrlHandlerDelegate _consoleCtrlHandler;

    static void Main(string[] args)
    {
        _consoleCtrlHandler += s =>
        {
            //DoCustomShutdownStuff();
            return false;   
        };
        SetConsoleCtrlHandler(_consoleCtrlHandler, true);
    }
}

Retornar FALSE do manipulador informa ao framework que não estamos "manipulando" o sinal de controle, e a próxima função do manipulador na lista de manipuladores para este processo é usada. Se nenhum dos manipuladores retorna TRUE, o manipulador padrão é chamado.

Observe que, quando o usuário realiza um logoff ou desligamento, o retorno de chamada não é chamado pelo Windows, mas é encerrado imediatamente.

BCA
fonte
3

Existe para aplicativos WinForms;

Application.ApplicationExit += CleanupBeforeExit;

Para aplicativos de console, tente

AppDomain.CurrentDomain.DomainUnload += CleanupBeforeExit;

Mas não tenho certeza de em que ponto isso é chamado ou se funcionará no domínio atual. Eu suspeito que não.

Rob Prouse
fonte
Os documentos de ajuda para DomainUnload dizem "O delegado EventHandler para este evento pode executar qualquer atividade de encerramento antes que o domínio do aplicativo seja descarregado." Portanto, parece que funciona no domínio atual. No entanto, pode não funcionar para sua necessidade porque seus tópicos podem manter o domínio ativo.
Rob Parker
2
Isso só lida com CTRL + C e CTRL + Close, não pega existe retornando, Environment.Exit nem clicando no botão Fechar.
Kit10
Não captura CTRL + C para mim com Mono no Linux.
starbeamrainbowlabs
2

Visual Studio 2015 + Windows 10

  • Permitir limpeza
  • Aplicativo de instância única
  • Alguns goldplating

Código:

using System;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;

namespace YourNamespace
{
    class Program
    {
        // if you want to allow only one instance otherwise remove the next line
        static Mutex mutex = new Mutex(false, "YOURGUID-YOURGUID-YOURGUID-YO");

        static ManualResetEvent run = new ManualResetEvent(true);

        [DllImport("Kernel32")]
        private static extern bool SetConsoleCtrlHandler(EventHandler handler, bool add);                
        private delegate bool EventHandler(CtrlType sig);
        static EventHandler exitHandler;
        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 ExitHandler(CtrlType sig)
        {
            Console.WriteLine("Shutting down: " + sig.ToString());            
            run.Reset();
            Thread.Sleep(2000);
            return false; // If the function handles the control signal, it should return TRUE. If it returns FALSE, the next handler function in the list of handlers for this process is used (from MSDN).
        }


        static void Main(string[] args)
        {
            // if you want to allow only one instance otherwise remove the next 4 lines
            if (!mutex.WaitOne(TimeSpan.FromSeconds(2), false))
            {
                return; // singleton application already started
            }

            exitHandler += new EventHandler(ExitHandler);
            SetConsoleCtrlHandler(exitHandler, true);

            try
            {
                Console.BackgroundColor = ConsoleColor.Gray;
                Console.ForegroundColor = ConsoleColor.Black;
                Console.Clear();
                Console.SetBufferSize(Console.BufferWidth, 1024);

                Console.Title = "Your Console Title - XYZ";

                // start your threads here
                Thread thread1 = new Thread(new ThreadStart(ThreadFunc1));
                thread1.Start();

                Thread thread2 = new Thread(new ThreadStart(ThreadFunc2));
                thread2.IsBackground = true; // a background thread
                thread2.Start();

                while (run.WaitOne(0))
                {
                    Thread.Sleep(100);
                }

                // do thread syncs here signal them the end so they can clean up or use the manual reset event in them or abort them
                thread1.Abort();
            }
            catch (Exception ex)
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.Write("fail: ");
                Console.ForegroundColor = ConsoleColor.Black;
                Console.WriteLine(ex.Message);
                if (ex.InnerException != null)
                {
                    Console.WriteLine("Inner: " + ex.InnerException.Message);
                }
            }
            finally
            {                
                // do app cleanup here

                // if you want to allow only one instance otherwise remove the next line
                mutex.ReleaseMutex();

                // remove this after testing
                Console.Beep(5000, 100);
            }
        }

        public static void ThreadFunc1()
        {
            Console.Write("> ");
            while ((line = Console.ReadLine()) != null)
            {
                if (line == "command 1")
                {

                }
                else if (line == "command 1")
                {

                }
                else if (line == "?")
                {

                }

                Console.Write("> ");
            }
        }


        public static void ThreadFunc2()
        {
            while (run.WaitOne(0))
            {
                Thread.Sleep(100);
            }

           // do thread cleanup here
            Console.Beep();         
        }

    }
}
AJBauer
fonte
Interessante que esta parece ser a resposta mais robusta. No entanto, tome cuidado ao alterar o tamanho do buffer do console: se a altura do buffer for menor que a altura da janela, o programa lançará uma exceção na inicialização.
John Zabroski de
1

O link mencionado acima por Charle B em comentário ao flq

No fundo diz:

SetConsoleCtrlHandler não funcionará no windows7 se você vincular ao user32

Em algum outro lugar do tópico, é sugerido criar uma janela oculta. Então eu crio um winform e no onload eu anexei ao console e executo o Main original. E então SetConsoleCtrlHandle funciona bem (SetConsoleCtrlHandle é chamado conforme sugerido por flq)

public partial class App3DummyForm : Form
{
    private readonly string[] _args;

    public App3DummyForm(string[] args)
    {
        _args = args;
        InitializeComponent();
    }

    private void App3DummyForm_Load(object sender, EventArgs e)
    {
        AllocConsole();
        App3.Program.OriginalMain(_args);
    }

    [DllImport("kernel32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool AllocConsole();
}
Jens
fonte
Na verdade, isso não funciona. Eu tenho um aplicativo WFP com várias janelas e uso o console ( AllocConsolecomo no seu exemplo) para mostrar algumas informações adicionais. O problema é que todo o aplicativo (todas as janelas) é fechado se o usuário clicar em (X) na janela do console. As SetConsoleCtrlHandlerobras, mas as paradas de aplicativos de qualquer maneira antes de qualquer código no manipulador executados (I ver breakpoints demitido e direita, em seguida, as paradas de aplicativos).
Mike Keskinov
Mas eu encontrei a solução que funciona para mim - eu simplesmente DESATIVE o botão Fechar. Veja: stackoverflow.com/questions/6052992/…
Mike Keskinov
0

Para os interessados ​​em VB.net. (Eu pesquisei na internet e não consegui encontrar um equivalente para isso) Aqui está traduzido para vb.net.

    <DllImport("kernel32")> _
    Private Function SetConsoleCtrlHandler(ByVal HandlerRoutine As HandlerDelegate, ByVal Add As Boolean) As Boolean
    End Function
    Private _handler As HandlerDelegate
    Private Delegate Function HandlerDelegate(ByVal dwControlType As ControlEventType) As Boolean
    Private Function ControlHandler(ByVal controlEvent As ControlEventType) As Boolean
        Select Case controlEvent
            Case ControlEventType.CtrlCEvent, ControlEventType.CtrlCloseEvent
                Console.WriteLine("Closing...")
                Return True
            Case ControlEventType.CtrlLogoffEvent, ControlEventType.CtrlBreakEvent, ControlEventType.CtrlShutdownEvent
                Console.WriteLine("Shutdown Detected")
                Return False
        End Select
    End Function
    Sub Main()
        Try
            _handler = New HandlerDelegate(AddressOf ControlHandler)
            SetConsoleCtrlHandler(_handler, True)
     .....
End Sub
dko
fonte
A solução acima não funciona para mim. O framework ControlEventType do vb.net 4.5 não está sendo resolvido.
Pude