C#如何在命令行应用程序中接收系统关闭或退出事件

5

我正在尝试,但到目前为止,我发现的所有内容似乎都围绕GUI和表单应用程序展开。这不是我所使用的。

最终它将成为一个服务,但在完成之前,我将其作为普通应用程序运行,问题在于如果我意外关闭它,它会留下另一个服务悬挂长达30分钟,数据不正确。我认为添加一个监听器来捕获应用程序关闭会很容易。

但我就是想不出来。


你想要另一个应用程序检测到这个应用程序关闭,还是你希望这个应用程序本身能够检测到它正在被关闭? - Matthew Watson
它将是WCF服务还是Windows服务? - Tilak
@MatthewWatson 这将是应用程序本身检测到它正在关闭,无论是由系统还是通过按控制台窗口上的红色 X。 - A.Grandt
@Tilak 有什么区别。它计划在Windows服务器上运行,但在我开始实施该部分之前还有几周时间。如果我记得正确,它提供了启动和停止方法。 - A.Grandt
我相信你可以简单地处理控制台应用退出事件 - Joshua Drake
@JoshuaDrake 我也尝试过那个方法,但它并没有及时触发。那个链接和其他评论似乎表明我的设置不会从中受益,因为我根本没有时间优雅地关闭。 - A.Grandt
4个回答

5

这里有一个完美的解决方案,可以满足您的需求:

在C#中检测控制台应用程序退出

该代码可以检测到所有可能导致应用程序关闭的事件:

  • Control+C
  • Control+Break
  • Close事件(使用关闭按钮“正常”关闭程序)
  • Logoff事件(注销使用)
  • Shutdown事件(系统关闭)
namespace Detect_Console_Application_Exit2
{
  class Program
  {
    private static bool isclosing = false;

    static void Main(string[] args)
    {
      SetConsoleCtrlHandler(new HandlerRoutine(ConsoleCtrlCheck), true);
      Console.WriteLine("CTRL+C,CTRL+BREAK or suppress the application to exit");
      while (!isclosing) ;
    }

    private static bool ConsoleCtrlCheck(CtrlTypes ctrlType)
    {
      // Put your own handler here
      switch (ctrlType)
      {
        case CtrlTypes.CTRL_C_EVENT:
          isclosing = true;
          Console.WriteLine("CTRL+C received!");
          break;

        case CtrlTypes.CTRL_BREAK_EVENT:
          isclosing = true;
          Console.WriteLine("CTRL+BREAK received!");
          break;

        case CtrlTypes.CTRL_CLOSE_EVENT:
          isclosing = true;
          Console.WriteLine("Program being closed!");
          break;

        case CtrlTypes.CTRL_LOGOFF_EVENT:
        case CtrlTypes.CTRL_SHUTDOWN_EVENT:
          isclosing = true;
          Console.WriteLine("User is logging off!");
          break;
      }
      return true;
    }

    #region unmanaged

    // Declare the SetConsoleCtrlHandler function
    // as external and receiving a delegate.
    [DllImport("Kernel32")]
    public static extern bool SetConsoleCtrlHandler(HandlerRoutine Handler, bool Add);

    // A delegate type to be used as the handler routine
    // for SetConsoleCtrlHandler.
    public delegate bool HandlerRoutine(CtrlTypes CtrlType);

    // An enumerated type for the control messages
    // sent to the handler routine.
    public enum CtrlTypes
    {
      CTRL_C_EVENT = 0,
      CTRL_BREAK_EVENT,
      CTRL_CLOSE_EVENT,
      CTRL_LOGOFF_EVENT = 5,
      CTRL_SHUTDOWN_EVENT
    }

    #endregion

    }
}

你在哪里遇到了异常,是什么类型的异常? - JotaBe
我可以在调试模式和发布模式下运行它。 - JotaBe
似乎是时间问题。一旦启动关闭程序,我就没有足够的时间了。我需要至少10-15秒钟来进行优雅的关闭。由于这只是为了在测试和初步开发期间防止问题,我认为最好确保人们不会错误地关闭应用程序,一旦我们转移到Windows服务,问题应该会自动解决。 - A.Grandt
感谢所有尝试协助的人,这些信息很可能以后会证明有用。 - A.Grandt
2
@A.Grandt 当您转移到Windows服务时,可以处理停止事件。如果您想成功,应该启动一个工作线程并从停止事件处理程序控制它。也就是说,当停止请求到达时,事件处理程序应通知工作线程应该停止,并等待其停止后再关闭服务。由于服务在停止事件处理程序中的代码执行完毕时关闭,因此您需要使用某种同步机制来通知工作线程应该停止,并等待其停止。 - JotaBe
显示剩余8条评论

2
尝试这个:

[编辑] 这是从上面链接的修改版本。

注意: 如果用户单击红色X关闭控制台窗口,则在应用程序被终止之前,您有非常有限的时间来作出响应! 如果运行以下程序,请查看消息框在终止之前显示了多长时间。

using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;


namespace Demo
{
    class Program
    {
        static void Main(string[] args)
        {
            SetConsoleCtrlHandler(ConsoleCtrlCheck, true);
            Console.WriteLine("CTRL+C,CTRL+BREAK or suppress the application to exit");

            while (!isclosing)
            {
                Thread.Sleep(1000);
            }
        }

        private static bool ConsoleCtrlCheck(CtrlTypes ctrlType)
        {
            // Put your own handling here:

            switch (ctrlType)
            {
                case CtrlTypes.CTRL_C_EVENT:

                    isclosing = true;

                    Console.WriteLine("CTRL+C received!");

                    break;

                case CtrlTypes.CTRL_BREAK_EVENT:

                    isclosing = true;

                    Console.WriteLine("CTRL+BREAK received!");

                    break;

                case CtrlTypes.CTRL_CLOSE_EVENT:

                    isclosing = true;

                    Console.WriteLine("Program being closed!");
                    MessageBox.Show("AHA!");

                    break;

                case CtrlTypes.CTRL_LOGOFF_EVENT:
                case CtrlTypes.CTRL_SHUTDOWN_EVENT:

                    isclosing = true;

                    Console.WriteLine("User is logging off!");

                    break;
            }

            return true;
        }

        #region unmanaged

        // Declare the SetConsoleCtrlHandler function as external and receiving a delegate.

        [DllImport("Kernel32")]

        public static extern bool SetConsoleCtrlHandler(HandlerRoutine Handler, bool Add);

        // A delegate type to be used as the handler routine for SetConsoleCtrlHandler.

        public delegate bool HandlerRoutine(CtrlTypes CtrlType);

        // An enumerated type for the control messages sent to the handler routine.

        public enum CtrlTypes
        {

            CTRL_C_EVENT = 0,
            CTRL_BREAK_EVENT,
            CTRL_CLOSE_EVENT,
            CTRL_LOGOFF_EVENT = 5,
            CTRL_SHUTDOWN_EVENT
        }

        #endregion

        private static bool isclosing;
    }
}

由于某些原因,这个失败了。请查看我在第一个答案中的评论以了解我的情况。 - A.Grandt
可能是因为进程在处理程序没有正常完成前被终止了。你需要有一些能够快速响应的东西,比如一个命名的EventWaitHandle,在程序关闭时只需发出信号即可。 - Matthew Watson
@A.Grandt - 如果你的程序链接到gdi.dll或user32.dll(在这种情况下回调将永远不会被调用),那么基于SetConsoleCtrlHandler的解决方案将无法工作。 - Jirka Hanika

1

如果要在应用程序内处理事件,

  1. 如果是 Windows 服务,可以使用 ServiceBase.OnStop

  2. 如果是 WCF 服务,可以使用 ServiceHost.Closed 事件。ServiceHost 从 Communication Host 继承 Closed(和相关事件)

如果要在单独的应用程序中处理事件,可以使用 Process.Exited 事件。

var serviceProcesses = Process.GetProcessesByName("service.exe");
if(serviceProcesses != null && serviceProcesses.Length>0)
{
    serviceProcesses[0].Exited += OnServiceClosed;
}

当我开始处理服务时,我会回来看这个。 - A.Grandt

0

AppDomain.CurrentDomain.ProcessExit事件?


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