Capture Console Exit C #

92

Ich habe eine Konsolenanwendung, die ziemlich viele Threads enthält. Es gibt Threads, die bestimmte Bedingungen überwachen und das Programm beenden, wenn sie wahr sind. Diese Kündigung kann jederzeit erfolgen.

Ich benötige ein Ereignis, das beim Schließen des Programms ausgelöst werden kann, damit ich alle anderen Threads bereinigen und alle Dateihandles und Verbindungen ordnungsgemäß schließen kann. Ich bin mir nicht sicher, ob bereits eines in das .NET Framework integriert ist, daher frage ich, bevor ich mein eigenes schreibe.

Ich habe mich gefragt, ob es ein Ereignis in der Art von:

MyConsoleProgram.OnExit += CleanupBeforeExit;
ZeroKelvin
quelle
2
Ich weiß, dass dies ein sehr später Kommentar ist, aber Sie müssen das nicht wirklich tun, wenn "Schließen von Dateien und Verbindungen" das einzige ist, was Sie als Bereinigung tun möchten. Weil Windows bereits alle mit einem Prozess verbundenen Handles während der Beendigung schließt.
Sedat Kapanoglu
6
^ Nur wenn diese Ressourcen dem zu beendenden Prozess gehören. Dies ist unbedingt erforderlich, wenn Sie beispielsweise eine versteckte COM-Anwendung (z. B. Word oder Excel) im Hintergrund automatisieren und sicherstellen müssen, dass sie beendet wird, bevor Ihre App beendet wird usw.
BrainSlugs83
1
Dies hat eine kurz aussehende Antwort stackoverflow.com/questions/2555292/…
Barlop

Antworten:

96

Ich bin nicht sicher, wo ich den Code im Web gefunden habe, aber ich habe ihn jetzt in einem meiner alten Projekte gefunden. Auf diese Weise können Sie Bereinigungscode in Ihrer Konsole ausführen, z. B. wenn diese abrupt geschlossen wird oder aufgrund eines Herunterfahrens ...

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

Aktualisieren

Für diejenigen, die die Kommentare nicht überprüfen, scheint es, dass diese spezielle Lösung unter Windows 7 nicht gut (oder überhaupt nicht) funktioniert . Der folgende Thread spricht darüber

flq
quelle
4
Können Sie damit den Exit abbrechen? Anders als wenn es heruntergefahren wird!
Ingh.am
7
Dies funktioniert großartig, nur bool Handler()muss return false;(es gibt nichts im Code zurück), damit es funktioniert. Wenn true zurückgegeben wird, fordert Windows das Dialogfeld "Prozess jetzt beenden" auf. = D
Cipi
3
Es sieht so aus, als ob diese Lösung mit Windows 7 für das Herunterfahren nicht funktioniert. Siehe social.msdn.microsoft.com/Forums/en/windowscompatibility/thread/…
CharlesB
3
Beachten Sie, dass beim Einfügen eines Haltepunkts in die 'Handler'-Methode eine NullReferenceException ausgelöst wird. Eingecheckt in VS2010, Windows 7.
Maxim
10
Dies funktionierte hervorragend für mich unter Windows 7 (64-Bit). Ich bin mir nicht sicher, warum alle das nicht sagen. Die einzigen größeren Änderungen, die ich vorgenommen habe, waren, die Anweisung enum und switch zu entfernen und "false" von der Methode zurückzugeben - ich mache alle meine Bereinigungen im Hauptteil der Methode.
BrainSlugs83
25

Vollständiges Arbeitsbeispiel, funktioniert mit Strg-C, schließt die Fenster mit X und tötet:

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
quelle
2
Ich habe dies unter Windows 7 getestet, mit HandlerAusnahme der return trueund einer while-Schleife, um Sekunden zu zählen. Die Anwendung läuft weiterhin auf Strg-C, wird jedoch nach 5 Sekunden geschlossen, wenn sie mit dem X geschlossen wird.
Antonios Hadjigeorgalis
Es tut mir leid, aber mit diesem Code kann ich "Bereinigung abgeschlossen" nur erhalten, wenn ich Strg + C drücke, nicht wenn ich mit der Taste 'X' schließe. Im letzteren Fall erhalte ich nur "System wird aufgrund von externem STRG-C oder Prozessabbruch oder Herunterfahren beendet", aber dann scheint es, dass die Konsole geschlossen wird, bevor der verbleibende Teil der HandlerMethode ausgeführt wird {unter Verwendung von Win10, .NET Framework 4.6.1}
Giacomo Pirinoli
8

Überprüfen Sie auch:

AppDomain.CurrentDomain.ProcessExit
jmservera
quelle
7
Dies scheint nur Exits von return oder Environment zu erfassen. Beim Beenden werden weder STRG + C, STRG + Unterbrechung noch die tatsächliche Schaltfläche zum Schließen auf der Konsole erfasst.
Kit10
Wenn Sie STRG + C separat behandeln mit Console.CancelKeyPressdann ProcessExitEreignis tatsächlich nach allen angehoben CancelKeyPressEvent - Handler Ausführung.
Konard
5

Ich hatte ein ähnliches Problem, nur meine Konsolen-App würde in einer Endlosschleife mit einer vorbeugenden Anweisung in der Mitte ausgeführt. Hier ist meine Lösung:

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
quelle
4

Klingt es so, als hätten Sie die Threads, die die Anwendung direkt beenden? Vielleicht ist es besser, wenn ein Thread dem Hauptthread signalisiert, dass die Anwendung beendet werden soll.

Bei Empfang dieses Signals kann der Haupt-Thread die anderen Threads sauber herunterfahren und sich schließlich selbst schließen.

rauben
quelle
3
Ich muss dieser Antwort zustimmen. Das Erzwingen des Beenden der Anwendung und das anschließende Aufräumen ist nicht der richtige Weg. Kontrollieren Sie Ihre Anwendung, Noit. Lass dich nicht kontrollieren.
Randolpho
1
Ein Thread, der direkt von mir erstellt wurde, ist nicht unbedingt das einzige, was meine Anwendung schließen kann. Strg-C und die "Schließen-Taste" sind andere Möglichkeiten, wie es enden kann. Der von Frank veröffentlichte Code passt nach geringfügigen Änderungen perfekt.
ZeroKelvin
4

Die Antwort von ZeroKelvin funktioniert in der Konsolen-App Windows 10 x64, .NET 4.6. Für diejenigen, die sich nicht mit der CtrlType-Enumeration befassen müssen, ist hier eine wirklich einfache Möglichkeit, sich in das Herunterfahren des Frameworks einzubinden:

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

Die Rückgabe von FALSE vom Handler teilt dem Framework mit, dass das Steuersignal nicht "verarbeitet" wird, und die nächste Handlerfunktion in der Liste der Handler für diesen Prozess wird verwendet. Wenn keiner der Handler TRUE zurückgibt, wird der Standardhandler aufgerufen.

Beachten Sie, dass der Rückruf nicht von Windows aufgerufen wird, wenn der Benutzer eine Abmeldung oder ein Herunterfahren durchführt, sondern sofort beendet wird.

BCA
quelle
3

Es gibt für WinForms Apps;

Application.ApplicationExit += CleanupBeforeExit;

Versuchen Sie es mit Konsolen-Apps

AppDomain.CurrentDomain.DomainUnload += CleanupBeforeExit;

Ich bin mir jedoch nicht sicher, wann das aufgerufen wird oder ob es innerhalb der aktuellen Domain funktioniert. Ich vermute nicht.

Rob Prouse
quelle
In den Hilfedokumenten für DomainUnload heißt es: "Der EventHandler-Delegat für dieses Ereignis kann alle Beendigungsaktivitäten ausführen, bevor die Anwendungsdomäne entladen wird." Es klingt also so, als würde es innerhalb der aktuellen Domäne funktionieren. Es funktioniert jedoch möglicherweise nicht für seine Bedürfnisse, da seine Threads die Domain möglicherweise aufrechterhalten.
Rob Parker
2
Dies behandelt nur STRG + C und STRG + Schließen. Es wird nicht über die Rückgabe, Environment.Exit oder das Klicken auf die Schaltfläche zum Schließen erfasst.
Kit10
Fängt STRG + C für mich mit Mono unter Linux nicht.
Starbeamrainbowlabs
2

Visual Studio 2015 + Windows 10

  • Bereinigen lassen
  • Einzelinstanz-App
  • Etwas vergoldet

Code:

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
quelle
Interessant, dass dies die robusteste Antwort zu sein scheint. Seien Sie jedoch vorsichtig beim Ändern der Konsolenpuffergröße: Wenn die Pufferhöhe kleiner als die Fensterhöhe ist, löst das Programm beim Start eine Ausnahme aus.
John Zabroski
1

Der Link oben erwähnt durch Charle B in Kommentar zu FLQ

Tief im Inneren sagt:

SetConsoleCtrlHandler funktioniert unter Windows 7 nicht, wenn Sie eine Verknüpfung zu user32 herstellen

An einigen Stellen im Thread wird empfohlen, ein verstecktes Fenster zu erstellen. Also erstelle ich eine Winform und habe sie im Onload an die Konsole angehängt und das ursprüngliche Main ausgeführt. Und dann funktioniert SetConsoleCtrlHandle einwandfrei (SetConsoleCtrlHandle wird wie von flq vorgeschlagen aufgerufen)

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
quelle
Eigentlich funktioniert das nicht. Ich habe eine WFP-App mit mehreren Fenstern und verwende die Konsole ( AllocConsolewie in Ihrem Beispiel), um einige zusätzliche Informationen anzuzeigen. Das Problem ist, dass die gesamte App (alle Windows) geschlossen wird, wenn der Benutzer im Konsolenfenster auf (X) klickt. Das SetConsoleCtrlHandlerfunktioniert, aber die App wird trotzdem angehalten, bevor Code im Handler ausgeführt wird (ich sehe Haltepunkte ausgelöst und sofort stoppt die App).
Mike Keskinov
Aber ich habe eine Lösung gefunden, die für mich funktioniert - ich habe einfach die Schaltfläche zum Schließen deaktiviert . Siehe: stackoverflow.com/questions/6052992/…
Mike Keskinov
0

Für alle, die sich für VB.net interessieren. (Ich habe im Internet gesucht und konnte kein Äquivalent dafür finden.) Hier wird es in vb.net übersetzt.

    <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
quelle
Die obige Lösung funktioniert bei mir nicht vb.net 4.5 Framework ControlEventType wird nicht aufgelöst. Ich konnte diese Idee als Lösung verwenden stackackflow.com/questions/15317082/…
glant