Cattura l'uscita dalla console C #


92

Ho un'applicazione console che contiene molti thread. Esistono thread che monitorano determinate condizioni e terminano il programma se sono vere. Questa risoluzione può avvenire in qualsiasi momento.

Ho bisogno di un evento che possa essere attivato alla chiusura del programma in modo da poter pulire tutti gli altri thread e chiudere correttamente tutti gli handle di file e le connessioni. Non sono sicuro che ce ne sia uno già integrato nel framework .NET, quindi me lo chiedo prima di scriverne uno mio.

Mi chiedevo se ci fosse un evento sulla falsariga di:

MyConsoleProgram.OnExit += CleanupBeforeExit;

2
So che questo è un commento molto tardivo, ma non è davvero necessario farlo se "chiudere file e connessioni" è l'unica cosa che vuoi fare come pulizia. Perché Windows chiude già tutti gli handle associati a un processo durante la terminazione.
Sedat Kapanoglu

6
^ Solo se quelle risorse sono di proprietà del processo che viene terminato. Questo è assolutamente necessario, se, ad esempio, stai automatizzando un'applicazione COM nascosta (ad esempio Word o Excel) in background e devi assicurarti di ucciderla prima che la tua app esca, ecc.
BrainSlugs83

1
questo ha una breve ricerca risposta stackoverflow.com/questions/2555292/...
barlop

Risposte:


96

Non sono sicuro di dove ho trovato il codice sul web, ma ora l'ho trovato in uno dei miei vecchi progetti. Ciò ti consentirà di eseguire la pulizia del codice nella tua console, ad esempio quando viene chiusa improvvisamente oa causa di uno spegnimento ...

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

Aggiornare

Per chi non controlla i commenti sembra che questa particolare soluzione non funzioni bene (o per niente) su Windows 7 . Il thread seguente parla di questo


4
Puoi usarlo per annullare l'uscita? Altro che per quando si sta spegnendo!
ingh.

7
Funziona alla grande, bool Handler()deve solo return false;(non restituisce nulla nel codice) in modo che funzioni. Se restituisce true, Windows richiede la finestra di dialogo "Terminate Process Now". = D
Cipi

3
Sembra che questa soluzione non funzioni con Windows 7 per l'evento di arresto, vedere social.msdn.microsoft.com/Forums/en/windowscompatibility/thread/…
CharlesB

3
Tieni presente che se inserisci un punto di interruzione nel metodo "Handler", verrà generata un'eccezione NullReferenceException. Controllato in VS2010, Windows 7.
Massimo

10
Questo ha funzionato alla grande per me su Windows 7 (64 bit). Non sono sicuro del motivo per cui tutti dicono che non lo fa. Le uniche modifiche importanti che ho apportato sono state di sbarazzarmi delle istruzioni enum e switch e di "restituire false" dal metodo: eseguo tutte le operazioni di pulizia nel corpo del metodo.
BrainSlugs83

25

Esempio di lavoro completo, funziona con ctrl-c, chiudendo le finestre con X e uccidendo:

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

2
L'ho provato su Windows 7 con tutto commentato Handlertranne che per il return trueciclo while per contare i secondi. L'applicazione continua a funzionare su ctrl-c ma si chiude dopo 5 secondi quando si chiude con la X.
Antonios Hadjigeorgalis

Mi dispiace ma utilizzando questo codice riesco ad ottenere "Cleanup complete" solo se premo Ctrl + C, non se chiudo con il tasto "X"; in quest'ultimo caso ottengo solo "Uscita dal sistema a causa di CTRL-C esterno, o processo kill, o shutdown" ma poi sembra che la console si chiuda prima di eseguire la parte rimanente del Handlermetodo {using Win10, .NET Framework 4.6.1}
Giacomo Pirinoli il

8

Controlla anche:

AppDomain.CurrentDomain.ProcessExit

7
Questo sembra solo catturare le uscite da return o Environment.Exit, non cattura CTRL + C, CTRL + Break, né il pulsante di chiusura effettivo sulla console.
Kit10

Se gestisci CTRL + C separatamente utilizzando Console.CancelKeyPressquindi l' ProcessExitevento effettivamente generato dopo l' CancelKeyPressesecuzione di tutti i gestori di eventi.
Konard

5

Ho avuto un problema simile, solo la mia app per console funzionava in ciclo infinito con una dichiarazione preventiva al centro. Ecco la mia soluzione:

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

4

Sembra che tu abbia i thread che terminano direttamente l'applicazione? Forse sarebbe meglio che un thread segnali il thread principale per dire che l'applicazione deve essere terminata.

Alla ricezione di questo segnale, il thread principale può chiudere in modo pulito gli altri thread e infine chiudersi.


3
Sono d'accordo con questa risposta. Forzare l'uscita dall'applicazione e poi provare a ripulire in seguito non è la strada da percorrere. Controlla la tua applicazione, Noit. Non lasciare che ti controlli.
Randolpho

1
Un thread generato da me direttamente non è necessariamente l'unica cosa che può chiudere la mia applicazione. Ctrl-C e il "pulsante di chiusura" sono altri modi in cui può terminare. Il codice pubblicato da Frank, dopo piccole modifiche, si adatta perfettamente.
ZeroKelvin

4

La risposta di ZeroKelvin funziona in Windows 10 x64, app console .NET 4.6. Per coloro che non hanno bisogno di gestire l'enumerazione CtrlType, ecco un modo davvero semplice per agganciarsi all'arresto del 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);
    }
}

Restituendo FALSE dal gestore si dice al framework che non stiamo "gestendo" il segnale di controllo, e viene utilizzata la successiva funzione di gestore nell'elenco dei gestori per questo processo. Se nessuno dei gestori restituisce TRUE, viene chiamato il gestore predefinito.

Notare che quando l'utente esegue una disconnessione o un arresto, la richiamata non viene chiamata da Windows ma viene invece terminata immediatamente.


3

C'è per le app WinForms;

Application.ApplicationExit += CleanupBeforeExit;

Per le app della console, prova

AppDomain.CurrentDomain.DomainUnload += CleanupBeforeExit;

Ma non sono sicuro a che punto viene chiamato o se funzionerà dall'interno del dominio corrente. Sospetto di no.


La documentazione della guida per DomainUnload dice "Il delegato EventHandler per questo evento può eseguire qualsiasi attività di terminazione prima che il dominio dell'applicazione venga scaricato". Quindi sembra che funzioni all'interno del dominio corrente. Tuttavia, potrebbe non funzionare per le sue necessità perché i suoi thread potrebbero mantenere attivo il dominio.
Rob Parker

2
Questo gestisce solo CTRL + C e CTRL + Chiudi, non intercetta esiste restituendo, Environment.Exit né facendo clic sul pulsante di chiusura.
Kit10

Non cattura CTRL + C per me con Mono su Linux.
starbeamrainbowlabs

2

Visual Studio 2015 + Windows 10

  • Consenti la pulizia
  • App a istanza singola
  • Qualche placcatura in oro

Codice:

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

    }
}

È interessante che questa sembra essere la risposta più robusta. Tuttavia, fai attenzione a modificare la dimensione del buffer della console: se l'altezza del buffer è inferiore all'altezza della finestra, il programma genererà un'eccezione all'avvio.
John Zabroski

1

Il collegamento citato sopra da Charle B nel commento a flq

In fondo dice:

SetConsoleCtrlHandler non funzionerà su windows7 se ci si collega a user32

In qualche altro punto del thread si suggerisce di creare una finestra nascosta. Quindi creo un winform e in onload mi collego alla console ed eseguo Main originale. E poi SetConsoleCtrlHandle funziona bene (SetConsoleCtrlHandle è chiamato come suggerito da 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();
}

In realtà questo non funziona. Ho un'app WFP multi-finestra e utilizzo la console ( AllocConsolecome nel tuo esempio) per mostrare alcune informazioni aggiuntive. Il problema è che l'intera app (tutte le finestre) viene chiusa se l'utente fa clic sulla (X) nella finestra della console. Le SetConsoleCtrlHandleropere, ma le soste app in ogni caso prima di qualsiasi codice nel gestore eseguito (vedo punti di interruzione sparato e destra e poi le soste app).
Mike Keskinov

Ma ho trovato una soluzione che funziona per me: ho semplicemente DISABILITATO il pulsante di chiusura. Vedi: stackoverflow.com/questions/6052992/...
Mike Keskinov

0

Per chi è interessato a VB.net. (Ho cercato in Internet e non sono riuscito a trovare un equivalente) Qui è tradotto in 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

La soluzione sopra non funziona per me framework vb.net 4.5 ControlEventType non viene risolto. Sono stato in grado di utilizzare questa idea come soluzione stackoverflow.com/questions/15317082/...
glant
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.