Ereignis zum Beenden der .NET-Konsolenanwendung

76

Gibt es in .NET eine Methode, z. B. ein Ereignis, um zu erkennen, wann eine Konsolenanwendung beendet wird? Ich muss einige Threads und COM-Objekte bereinigen.

Ich führe eine Nachrichtenschleife ohne Formular über die Konsolenanwendung aus. Eine DCOM-Komponente, die ich verwende, scheint zu erfordern, dass die Anwendung Nachrichten meldet.

Ich habe versucht, Process.GetCurrentProcess.Exited und Process.GetCurrentProcess.Disposed einen Handler hinzuzufügen.

Ich habe auch versucht, Application.ApplicationExit- und Application.ThreadExit-Ereignissen einen Handler hinzuzufügen, aber sie werden nicht ausgelöst. Vielleicht liegt das daran, dass ich kein Formular verwende.

user79755
quelle
2
Das Ereignis Process.GetCurrentProcess (). Exited sollte ausgelöst werden, wenn Sie Process.EnableRaisingEvents = true zum ersten Mal festlegen. Andernfalls wird es nicht ausgelöst.
Triynko
2
Mögliches Duplikat von Capture Console Exit C #
Nawfal
@Triynko wie ist das? Process.Exited wird ausgelöst, nachdem der Prozess beendet wurde. Und wenn die eigene Anwendung beendet wird, wird überhaupt kein Code ausgeführt. Ziemlich nutzlos imo ..
Nawfal
Vollständiges Arbeitsbeispiel gegen Ende dieses Beitrags in C #
JJ_Coder4Hire

Antworten:

77

Sie können das ProcessExitEreignis des 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");
    }
}

Aktualisieren

Hier ist ein vollständiges Beispielprogramm mit einer leeren "Nachrichtenpumpe", die in einem separaten Thread ausgeführt wird und es dem Benutzer ermöglicht, einen Beendigungsbefehl in die Konsole einzugeben, um die Anwendung ordnungsgemäß zu schließen. Nach der Schleife in MessagePump möchten Sie wahrscheinlich die vom Thread verwendeten Ressourcen auf nette Weise bereinigen. Dies ist aus mehreren Gründen besser als in ProcessExit:

  • Vermeiden Sie Cross-Threading-Probleme. Wenn im MessagePump-Thread externe COM-Objekte erstellt wurden, ist es dort einfacher, mit ihnen umzugehen.
  • ProcessExit ist zeitlich begrenzt (standardmäßig 3 Sekunden). Wenn die Bereinigung zeitaufwändig ist, kann dies fehlschlagen, wenn sie in dieser Ereignisbehandlungsroutine ausgeführt wird.

Hier ist der Code:

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");
    }
}
Fredrik Mörk
quelle
7
Danke für den Vorschlag Fredrik. Dieses Ereignis scheint auch nicht ausgelöst zu werden.
user79755
Wie wird Ihr Prozess beendet? Rufen Sie einen Code auf, der zum Beenden führt?
Fredrik Mörk
3
Ah .. das macht Sinn; Sie beenden die Anwendung nicht, Sie töten sie mit brutaler Gewalt. Es gibt kein Ereignis, das das aufgreift. Sie müssen eine Methode implementieren, mit der Sie Ihre Anwendung ordnungsgemäß beenden können.
Fredrik Mörk
3
Sind Sie im Ernst? Wenn eine Windows.Forms-Anwendung von einem Benutzer geschlossen wird, der das Hauptformular schließt, haben Sie sicherlich die Möglichkeit, eine Bereinigung durchzuführen! Ich implementiere einen Batch und es ist völlig inakzeptabel, dass der Prozess nur auf mir stirbt, ohne die Möglichkeit zu haben, die Tatsache zu protokollieren, dass der Job abgebrochen wurde ...
The Dag
2
Das funktioniert nicht. fügte es in ein neues Konsolenprojekt ein, löste die Abhängigkeiten, setzte einen Haltepunkt auf CurrentDomain_ProcessExit und es wird nicht ausgelöst, wenn STRG-C gedrückt wird oder wenn auf das X im Fenster geklickt wird. Siehe meine vollständige Arbeitsantwort unten
JJ_Coder4Hire
43

Hier ist eine vollständige, sehr einfache .Net-Lösung, die in allen Windows-Versionen funktioniert. Fügen Sie es einfach in ein neues Projekt ein, führen Sie es aus und versuchen Sie STRG-C, um zu sehen, wie es damit umgeht:

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..");
        }
    }
 }
JJ_Coder4Hire
quelle
2
Dies scheint die beste C # -Lösung zu sein, die für alle Exit-Szenarien funktioniert! Funktioniert perfekt! Danke :)
HansLindgren
Der Code funktioniert nicht in Windows XP SP3. wenn wir die Anwendung vom Task-Manager schließen.
Avinash
4
Funktioniert dieser Code unter Linux in einer .NET Core-Konsolenanwendung? Ich habe es nicht versucht, aber "Kernel32" scheint mir nicht portabel zu sein ...
ManuelJE
Ausgezeichnet. Hat perfekt für mich funktioniert. Normalerweise bin ich in Java, aber an einem Client-Standort musste ich eine Multithread-C # -Konsolen-App schreiben, und ich kenne C # nicht so gut, das eine Verbindung zu einer Echtzeit-Marktdaten-Feed-API herstellt. Ich muss sicherstellen, dass ich Dinge bereinige, andere Server-Sachen einrichte usw. Dies ist eine gute Lösung für mich.
TilleyTech
1
Ich habe das erfolgreich genutzt. Ich habe jedoch ein ManualResetEvent verwendet, um zu signalisieren, wann mit dem Herunterfahren begonnen werden soll. (Und ein weiteres ManualResetEvent wartete in Handler, um zu signalisieren, wann das Herunterfahren beendet war.)
Mike Fisher
18

Die Anwendung ist ein Server, der einfach ausgeführt wird, bis das System heruntergefahren wird oder Strg + C empfängt oder das Konsolenfenster geschlossen wird.

Aufgrund des außergewöhnlichen Charakters der Anwendung ist es nicht möglich, "ordnungsgemäß" zu beenden. (Es kann sein, dass ich eine andere Anwendung codieren könnte, die eine Meldung zum "Herunterfahren des Servers" sendet, dies jedoch für eine Anwendung übertrieben und für bestimmte Umstände, z. B. wenn der Server (tatsächliches Betriebssystem) tatsächlich heruntergefahren wird, immer noch unzureichend ist.)

Aufgrund dieser Umstände habe ich einen " ConsoleCtrlHandler " hinzugefügt, in dem ich meine Threads stoppe und meine COM-Objekte bereinige usw.


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

Dieses Setup scheint perfekt zu funktionieren. Hier ist ein Link zu einem C # -Code für dasselbe.

user79755
quelle
1
Danke, es hat mir geholfen :) +1 Du solltest deine eigene Antwort als DIE Antwort markieren.
Leppie
4
Es scheint eine Grenze von 3 Sekunden zu geben, bis der Handler fertig ist. Wenn Sie nicht rechtzeitig fertig sind, werden Sie unanständig gekündigt. Das schließt ein, wenn Sie auf einem Haltepunkt im Debugger sitzen!
Dan-Gph
Dies funktioniert, aber ein vollständiges Arbeitsbeispiel finden Sie unten in C #
JJ_Coder4Hire
1
Dieser Code wird nicht ausgelöst, wenn Sie End Process im Task-Manager verwenden.
Acaz Souza
1
@ dan-gph Das Programm wird beendet, wenn der Haupt-Thread beendet ist. Fügen Sie etwas hinzu wie // Halten Sie die Konsole, damit sie nicht am Ende abläuft, während (! ExitSystem) {Thread.Sleep (500); }
Susan Wang
8

Für den Fall STRG + C können Sie Folgendes verwenden:

// 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);
}
EricLaw
quelle
Behandelt dies das Herunterfahren der Abmeldung Strg + C usw.?
user79755
1
Versuchte es, das Ereignis wird nie ausgelöst. Gleich wie die anderen Ereignisse.
user79755
1

Können Sie die WM_QUIT-Nachricht nicht verwenden, wenn Sie eine Konsolenanwendung verwenden und Nachrichten pumpen?

Mike Dinescu
quelle
1
Darüber wundere ich mich auch. Ich nehme an, es könnte der Fall sein, dass Windows die Nachricht nicht an eine Konsolen-App sendet, aber es scheint mir seltsam. Man sollte denken, die Anforderung wäre, eine Nachrichtenpumpe zu haben, nicht ob sie eine GUI hat oder nicht ...
The Dag
-1

Als gutes Beispiel kann es sich lohnen, zu diesem Projekt zu navigieren und zu sehen, wie das Beenden von Prozessen grammatikalisch oder in diesem hier gefundenen Snippet von VM behandelt wird

                ConsoleOutputStream = new ObservableCollection<string>();

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