89

In .NET, is there a method, such as an event, for detecting when a Console Application is exiting? I need to clean up some threads and COM objects.

I am running a message loop, without a form, from the console application. A DCOM component that I am using seems to require that the application pump messages.

I have tried adding a handler to Process.GetCurrentProcess.Exited and Process.GetCurrentProcess.Disposed.

I have also tried adding a handler to Application.ApplicationExit and Application.ThreadExit events, but they are not firing. Perhaps that is because I am not using a form.

SammuelMiranda
  • 341
  • 1
  • 22
user79755
  • 2,363
  • 4
  • 28
  • 36
  • 2
    The Process.GetCurrentProcess().Exited event should fire if you first set Process.EnableRaisingEvents = true; otherwise, it won't fire. – Triynko Apr 22 '13 at 23:54
  • 2
    possible duplicate of [Capture console exit C#](http://stackoverflow.com/questions/474679/capture-console-exit-c-sharp) – nawfal Jan 10 '14 at 09:01
  • 1
    @Triynko how is that? Process.Exited is fired after the process is exited. And if the own application is exited then there is no code executed at all. Pretty useless imo.. – nawfal Jan 10 '14 at 16:25
  • Complete working example near the end of this post in C# – JJ_Coder4Hire Apr 10 '14 at 18:47
  • https://stackoverflow.com/questions/4646827/on-exit-for-a-console-application – Ben Mar 09 '18 at 20:51

5 Answers5

88

You can use the ProcessExit event of the 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");
    }
}

Update

Here is a full example program with an empty "message pump" running on a separate thread, that allows the user to input a quit command in the console to close down the application gracefully. After the loop in MessagePump you will probably want to clean up resources used by the thread in a nice manner. It's better to do that there than in ProcessExit for several reasons:

  • Avoid cross-threading problems; if external COM objects were created on the MessagePump thread, it's easier to deal with them there.
  • There is a time limit on ProcessExit (3 seconds by default), so if cleaning up is time consuming, it may fail if pefromed within that event handler.

Here is the 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
  • 147,210
  • 26
  • 277
  • 333
  • 9
    Thanks for the suggestion Fredrik. This event does not appear to fire either. – user79755 Jul 13 '09 at 14:48
  • How does your process exit? Do you call any code that makes it exit? – Fredrik Mörk Jul 13 '09 at 14:51
  • Currently the only way to exit, is the to click the Close button on the console window or perhaps press ctrl+c. – user79755 Jul 13 '09 at 14:53
  • 3
    Ah.. that makes sense; you are not exiting the application, you are killing it with brute force. There is no event that will pick that up. You need to implement a way of gracefully quitting your application. – Fredrik Mörk Jul 13 '09 at 14:56
  • 4
    Are you serious? Surely when any Windows.Forms application is closed by a user closing the main form you have a chance to perform some cleanup! I am implementing a batch and it's completely unacceptable to just have the process die on me with no chance to even log the fact that the the job was aborted... – The Dag Jul 25 '12 at 11:23
  • The corresponding use of ProcessExit() for VB.NET is in *[Closing function in a VB.NET console application](http://stackoverflow.com/questions/10099377)*. – Peter Mortensen Nov 20 '13 at 15:29
  • 3
    This does not work. pasted it into a new console project, resolved the dependencies, put a break point on CurrentDomain_ProcessExit and it does not fire when CTRL-C is pushed, or when the X on the window is clicked. See my complete working response below – JJ_Coder4Hire Apr 10 '14 at 18:44
  • @TheDag This isn't a problem of .NET. In Windows Vista, the console application has been changed, so that instead of getting a message when the user clicks the close button, the process is basically killed. Ctrl-C *can* be intercepted, though - using the `Console.CancelKeyPress` event. – Luaan Oct 21 '15 at 06:24
  • This neglects many termination scenarios. JJ_Coder4Hire's solution handles everything. – Digiproc Mar 13 '20 at 11:30
  • Doesn't work at all. – Switch Nov 28 '20 at 04:38
  • In what context does it not work, @Switch? To the best of my recollection, it worked well on the .NET framework that was current when I posted this, 11 years ago. – Fredrik Mörk Nov 28 '20 at 17:23
  • It never fires the event CurrentDomain_ProcessExit. The one JJ_Coder4Hire posted below works perfectly. – Switch Nov 28 '20 at 22:05
53

Here is a complete, very simple .Net solution that works in all versions of windows. Simply paste it into a new project, run it and try CTRL-C to view how it handles it:

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
  • 4,139
  • 1
  • 32
  • 23
  • 3
    This seems to be the best C# solution that works for all exit scenarios! Works perfectly! Thanks :) – HansLindgren Nov 13 '16 at 15:34
  • 1
    the code dosen't works in windows xp sp3. if we close the application from task manager. – Avinash Dec 05 '16 at 14:11
  • 5
    Will this code work on Linux in a .NET Core console application ? I didn't try but "Kernel32" seems not portable to me... – ManuelJE Jan 31 '19 at 14:35
  • Excellent. Worked perfectly for me. I'm usually in Java land but at a client site I've had to write a multi-threaded C# console app, and I don't know C# quite as well, that connects to a realtime market data feed API. I need to make sure I clean stuff up, set some other server stuff etc. This is a good solution for me. – TilleyTech Apr 25 '19 at 07:59
  • 1
    I used this successfully. However, I used a ManualResetEvent to signal when to begin shutting down. (And another ManualResetEvent waited on in Handler to signal when the shut down finished.) – Mike Fisher Jul 31 '19 at 05:44
  • In the case user click on console close button Thread.Sleep(5000000) not working and console will close after wait for 5 sec. So this code is not completely work. – Ali.P Oct 26 '20 at 08:15
19

The application is a server which simply runs until the system shuts down or it receives a Ctrl+C or the console window is closed.

Due to the extraordinary nature of the application, it is not feasible to "gracefully" exit. (It may be that I could code another application which would send a "server shutdown" message but that would be overkill for one application and still insufficient for certain circumstances like when the server (Actual OS) is actually shutting down.)

Because of these circumstances I added a "ConsoleCtrlHandler" where I stop my threads and clean up my COM objects 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

This setup seems to work out perfectly. Here is a link to some C# code for the same thing.

user79755
  • 2,363
  • 4
  • 28
  • 36
  • 1
    Thanks, it helped me :) +1 You should mark your own answer as THE answer. – leppie Feb 17 '10 at 19:46
  • 4
    There seems to be a 3 second limit for the handler to finish. If you don't finish in time, you will be ungracefully terminated. That includes if you are sitting on a breakpoint in the debugger! – dan-gph Aug 04 '10 at 03:10
  • This works but a complete working sample is found below in C# – JJ_Coder4Hire Apr 10 '14 at 18:45
  • 1
    This code is not fired if you use End Process in Task Manager. – Acaz Souza Nov 10 '14 at 15:48
  • 1
    @dan-gph The program terminates when the main thread finishes. Add something like //hold the console so it doesn’t run off the end while(!exitSystem) { Thread.Sleep(500); } – Susan Wang Jan 21 '19 at 04:53
  • ControlHandler will terminate after 5 sec, Even when clean up code not finished yet. – Ali.P Oct 26 '20 at 08:51
8

For the CTRL+C case, you can use this:

// 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
  • 54,427
  • 7
  • 140
  • 182
2

If you are using a console application and you are pumping messages, can't you use the WM_QUIT message?

Mike Dinescu
  • 48,812
  • 10
  • 104
  • 136
  • 1
    I wonder about that too. I suppose it *could* be the case that windows doesn't send the message to a console app, but it seems weird to me. One should think the requirement would be to have a message pump, not whether or not it has a GUI... – The Dag Jul 25 '12 at 11:25