.NET控制台应用程序退出事件

107
在.NET中,是否有一种方法(例如事件)可以检测控制台应用程序退出?我需要清理一些线程和COM对象。
我正在从控制台应用程序中运行一个没有窗体的消息循环。我使用的一个DCOM组件似乎要求应用程序泵送消息。
我尝试添加一个处理程序到Process.GetCurrentProcess.Exited和Process.GetCurrentProcess.Disposed。
我还尝试添加一个处理程序到Application.ApplicationExit和Application.ThreadExit事件,但它们没有触发。也许这是因为我没有使用窗体。

2
如果您首先设置Process.EnableRaisingEvents = true,则应触发Process.GetCurrentProcess().Exited事件;否则,它将不会触发。 - Triynko
2
可能是捕获控制台退出 C#的重复问题。 - nawfal
1
@Triynko 这个怎么样?Process.Exited 是在进程退出后触发的。如果自己的应用程序退出了,那么根本不会执行任何代码。我认为这很无用。 - nawfal
本帖末尾有C#的完整工作示例。 - JJ_Coder4Hire
https://dev59.com/WW445IYBdhLWcg3w5OIH - Ben
5个回答

115
你可以使用AppDomainProcessExit事件:
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");
    }
}

更新

这是一个完整的示例程序,其中一个空的“消息泵”在单独的线程上运行,允许用户在控制台中输入退出命令以正常关闭应用程序。在 MessagePump 循环之后,您可能希望以良好的方式清理线程使用的资源。最好在那里做这件事,而不是在 ProcessExit 中做,原因如下:

  • 避免跨线程问题;如果在 MessagePump 线程上创建了外部 COM 对象,则更容易在那里处理它们。
  • ProcessExit 有时间限制(默认为 3 秒),因此如果清理需要时间,则在该事件处理程序中执行可能会失败。

以下是代码:

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

11
谢谢你的建议,Fredrik。但是这个事件似乎也没有触发。 - user79755
3
啊.. 那就说得通了;你并没有退出应用程序,而是用蛮力结束了它。这样做没有任何事件能够捕捉到。你需要实现一种优雅退出应用程序的方法。 - Fredrik Mörk
4
你是认真的吗?当用户关闭主窗体时,任何Windows.Forms应用程序都有机会执行一些清理工作!我正在实现一个批处理任务,如果进程突然死掉而没有机会记录作业被中止的事实,那将是完全不可接受的。 - The Dag
VB.NET 中 ProcessExit() 的相应使用在 VB.NET 控制台应用程序的关闭函数 中。 - Peter Mortensen
4
这不起作用。我将其粘贴到一个新的控制台项目中,解决了依赖关系,在CurrentDomain_ProcessExit上设置了断点,但当按下CTRL-C或单击窗口上的X时,它并没有触发。请参见我的完整工作响应如下。 - JJ_Coder4Hire
显示剩余6条评论

62

这是一个完整的、非常简单的.NET解决方案,可在所有版本的Windows中运行。只需将其粘贴到新项目中,运行它并尝试使用Ctrl + C查看它如何处理:

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

3
这似乎是最好的 C# 解决方案,适用于所有退出情况!非常完美!谢谢 :) - HansLindgren
1
代码在Windows XP SP3上无法运行。如果我们从任务管理器中关闭应用程序,则会出现此问题。 - Avinash
8
这段代码能在 .NET Core 控制台程序中的 Linux 上运行吗?我没有试过,但是 "Kernel32" 看起来对我来说不可移植... - ManuelJE
1
我成功地使用了这个。但是,我使用了一个ManualResetEvent来指示何时开始关闭。(另一个ManualResetEvent在Handler中等待信号,以指示关闭何时完成。) - Mike Fisher
2
如果用户点击控制台关闭按钮,Thread.Sleep(5000000)将无法工作,并且控制台将在等待5秒后关闭。因此,这段代码并不完全有效。 - Ali Poustdouzan
显示剩余3条评论

21

这个应用程序是一个服务器,只要系统关闭或者它接收到 Ctrl+C 或者控制台窗口被关闭,它就会一直运行。

由于这个应用程序的特殊性质,它不可避免地无法“优雅”地退出。(可能可以编写另一个应用程序来发送“服务器关闭”消息,但这对于一个应用程序来说过于繁琐,并且在某些情况下仍然不足以解决问题,比如当服务器(实际上是操作系统)正在关闭时。)

因此,我添加了一个“ConsoleCtrlHandler”,在其中停止我的线程并清理我的 COM 对象等...


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

这个设置似乎完美地解决了问题。在这里有一个链接,链接到相同功能的一些 C# 代码。


1
谢谢,它帮了我 :) +1 你应该将自己的答案标记为“答案”。 - leppie
4
处理程序好像有一个3秒的限制来完成操作。如果你没有及时完成,处理程序将会被强制终止。这也包括在调试器中打断点的情况! - dan-gph
这个可以工作,但是完整的工作示例在下面的C#中找到。 - JJ_Coder4Hire
1
如果您在任务管理器中使用“结束进程”功能,此代码将不会被触发。 - Acaz Souza
1
@dan-gph 当主线程结束时,程序将终止。添加以下内容: //hold the console so it doesn’t run off the end while(!exitSystem) { Thread.Sleep(500); } 以防止控制台窗口意外关闭。 - Susan Wang
ControlHandler 将在 5 秒后终止,即使清理代码尚未完成。 - Ali Poustdouzan

11

对于CTRL+C的情况,你可以使用以下代码:

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

这个处理关机、注销、Ctrl+C等吗? - user79755
1
尝试过了,事件从未触发。与其他事件相同。 - user79755

2

如果您正在使用控制台应用程序并且正在发送消息,则可以使用WM_QUIT消息。

注:本句中pump messages一词不好翻译,根据上下文和it技术常识猜测为“发送消息”的意思。

1
我也对此感到疑惑。我想这可能是Windows没有将消息发送到控制台应用程序的原因,但这对我来说似乎很奇怪。人们应该认为要求是有一个消息泵,而不是它是否具有GUI... - The Dag

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接