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


77

在.NET中,是否有一种方法(例如事件)来检测控制台应用程序何时退出?我需要清理一些线程和COM对象。

我正在从控制台应用程序运行没有表单的消息循环。我正在使用的DCOM组件似乎要求应用程序泵送消息。

我尝试将处理程序添加到Process.GetCurrentProcess.Exited和Process.GetCurrentProcess.Disposed。

我也尝试过向Application.ApplicationExit和Application.ThreadExit事件添加处理程序,但是它们没有触发。也许那是因为我没有使用表格。


2
如果您首先设置Process.EnableRaisingEvents = true,则应该触发Process.GetCurrentProcess()。Exited事件。否则,它不会开火。
Triynko


@Triynko怎么样?退出进程后将触发Process.Exited。而且,如果退出自己的应用程序,则根本不会执行任何代码。相当没用的imo ..
nawfal 2014年

在C#中此帖子结尾附近的完整工作示例
JJ_Coder4Hire 2014年

Answers:


78

您可以使用以下ProcessExit事件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");
    }
}

更新资料

这是一个完整的示例程序,其中的空“消息泵”运行在单独的线程上,允许用户在控制台中输入quit命令以正常关闭应用程序。在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");
    }
}

7
感谢弗雷德里克的建议。此事件似乎也不触发。
user79755

您的流程如何退出?您是否调用任何使其退出的代码?
FredrikMörk,2009年

3
啊..那很有道理;您没有退出该应用程序,而是用蛮力将其杀死。没有任何事件可以解决这一问题。您需要实现一种正常退出应用程序的方式。
FredrikMörk,2009年

3
你是认真的吗?当然,当用户关闭主窗体关闭了任何Windows.Forms应用程序时,您都有机会执行清除操作!我正在执行一个批处理,只是让过程死在我身上,甚至没有机会记录该工作已被中止的事实,这是完全不可接受的……
The Dag 2012年

2
这是行不通的。将其粘贴到新的控制台项目中,解决依赖关系,在CurrentDomain_ProcessExit上放置一个断点,当按下CTRL-C或单击窗口上的X时,它不会触发。请在下面查看我的完整工作回复
JJ_Coder4Hire 2014年

43

这是一个适用于所有Windows版本的完整,非常简单的.Net解决方案。只需将其粘贴到一个新项目中,运行它,然后尝试按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..");
        }
    }
 }

2
这似乎是适用于所有退出方案的最佳C#解决方案!完美的作品!谢谢:)
HansLindgren

该代码不适用于Windows XP SP3。如果我们从任务管理器关闭应用程序。
阿维纳什(Avinash)2013年

4
这段代码可以在.NET Core控制台应用程序中的Linux上运行吗?我没有尝试过,但“ Kernel32”似乎对我来说并不便于携带……
ManuelJE

优秀。为我完美地工作。我通常在Java领域,但是在客户站点上,我不得不编写一个多线程C#控制台应用程序,而且我也不十分了解C#,它连接到实时市场数据Feed API。我需要确保清理干净,设置其他服务器内容等。这对我来说是一个很好的解决方案。
TilleyTech

1
我成功使用了它。但是,我使用ManualResetEvent发出何时开始关闭的信号。(还有另一个ManualResetEvent在Handler中等待,以在关闭完成时发出信号。)
Mike Fisher

18

该应用程序是一台服务器,仅在系统关闭或收到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 2010年

4
处理程序完成的时间似乎有3秒的限制。如果您没有及时完成任务,您将被无礼终止。这包括您是否坐在调试器的断点上!
dan-gph 2010年

此方法有效,但下面的C#中提供了完整的工作示例
JJ_Coder4Hire 2014年

1
如果您在任务管理器中使用“结束进程”,则不会触发此代码。
Acaz Souza 2014年

1
@ dan-gph当主线程完成时,程序终止。添加类似// //保持控制台,这样它就不会在结束时运行while(!exitSystem){Thread.Sleep(500); }
Susan Wang

8

对于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
尝试过,该事件永远不会触发。与其他事件相同。
2009年

1

如果您正在使用控制台应用程序并且正在泵送消息,那么您不能使用WM_QUIT消息吗?


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

-1

作为一个很好的例子,值得导航到该项目并查看如何以语法方式处理退出的进程,或者在此处的VM片段中查看

                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;
    }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.