【问题标题】:Capture console exit C#捕获控制台退出 C#
【发布时间】:2010-10-03 05:54:31
【问题描述】:

我有一个包含很多线程的控制台应用程序。有些线程监视某些条件并在它们为真时终止程序。这种终止可能随时发生。

我需要一个可以在程序关闭时触发的事件,以便我可以清理所有其他线程并正确关闭所有文件句柄和连接。我不确定 .NET 框架中是否已经内置了一个,所以我在编写自己的框架之前先问一下。

我想知道是否有类似的事件:

MyConsoleProgram.OnExit += CleanupBeforeExit;

【问题讨论】:

  • 我知道这是一个很晚的评论,但如果“关闭文件和连接”是您唯一想做的清理工作,那么您实际上不需要这样做。因为 Windows 在终止期间已经关闭了与进程关联的所有句柄。
  • ^ 仅当这些资源归被终止的进程所有时。这是绝对必要的,例如,如果您正在后台自动化隐藏的 COM 应用程序(例如 Word 或 Excel),并且您需要确保在应用程序退出之前将其杀死,等等。
  • 这有一个简短的答案stackoverflow.com/questions/2555292/…

标签: c# .net events console exit


【解决方案1】:

完整的工作示例,使用 ctrl-c,使用 X 关闭窗口并杀死:

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

【讨论】:

  • 我在 Windows 7 上测试了这个,除了 return true 和一个 while 循环来计算秒数之外,所有的东西都被 Handler 注释掉了。应用程序继续在 ctrl-c 上运行,但在使用 X 关闭时会在 5 秒后关闭。
  • 我很抱歉,但使用此代码,我只有在按 Ctrl+C 时才能获得 "Cleanup complete",而不是在我使用“X”按钮关闭时;在后一种情况下,我只得到 "Exiting system due to external CTRL-C, or process kill, or shutdown" 但是在执行Handler方法的剩余部分之前控制台似乎关闭了{使用Win10、.NET Framework 4.6.1}
  • 在 Windows 10 上为我工作 CTRL-C,X 在窗口上并在任务管理器中结束进程。
  • @JJ_Coder4Hire 如果您在任务管理器中选择根/父控制台进程并单击End Process,它对您有用吗?对我来说,它只有在我选择子控制台进程时才有效(有关更多信息,请参阅my question)。
  • 谢谢。它在 Windows 10 上非常适合我。
【解决方案2】:

Charle B 在对 flq 的评论中提到的 link

内心深处说:

如果您链接到 user32,SetConsoleCtrlHandler 将无法在 windows7 上运行

建议在线程中的其他地方创建一个隐藏窗口。因此,我创建了一个 winform,并在 onload 中附加到控制台并执行原始 Main。 然后 SetConsoleCtrlHandle 工作正常(按照 flq 的建议调用 SetConsoleCtrlHandle)

public partial class App3DummyForm : Form
{
    private readonly string[] _args;

    public App3DummyForm(string[] args)
    {
        _args = args;
        InitializeComponent();
    }

    private void App3DummyForm_Load(object sender, EventArgs e)
    {
        AllocConsole();
        App3.Program.OriginalMain(_args);
    }

    [DllImport("kernel32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool AllocConsole();
}

【讨论】:

  • 其实这行不通。我有多窗口 WFP 应用程序,我使用控制台(如您的示例中的AllocConsole)来显示一些附加信息。问题是,如果用户单击控制台窗口上的 (X),整个应用程序(所有 Windows)都会关闭。 SetConsoleCtrlHandler 有效,但应用程序在处理程序中的任何代码执行之前都会停止(我看到断点被触发,然后应用程序停止)。
  • 但我找到了适合我的解决方案——我简单的 DISABLED 关闭按钮。见:stackoverflow.com/questions/6052992/…
【解决方案3】:

我不确定我在网络上的哪里找到了代码,但我现在在我的一个旧项目中找到了它。这将允许您在控制台中执行清理代码,例如当它突然关闭或由于关机时......

[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)
{
  switch (sig)
  {
      case CtrlType.CTRL_C_EVENT:
      case CtrlType.CTRL_LOGOFF_EVENT:
      case CtrlType.CTRL_SHUTDOWN_EVENT:
      case CtrlType.CTRL_CLOSE_EVENT:
      default:
          return false;
  }
}


static void Main(string[] args)
{
  // Some biolerplate to react to close window event
  _handler += new EventHandler(Handler);
  SetConsoleCtrlHandler(_handler, true);
  ...
}

更新

对于那些不检查 cmets 的人来说,这个特定的解决方案似乎不能Windows 7 上运行良好(或根本不运行)。下面thread讲这个

【讨论】:

  • 可以用这个取消退出吗?除了关闭时!
  • 这很好用,只有bool Handler() 必须return false; (它在代码中不返回任何内容)所以它可以工作。如果它返回 true,windows 会提示“Terminate Process Now”对话框。 =D
  • 看来此解决方案不适用于 Windows 7 的关机事件,请参阅 social.msdn.microsoft.com/Forums/en/windowscompatibility/thread/…
  • 请注意,如果您在“处理程序”方法中放置断点,它将引发 NullReferenceException。签入 VS2010,Windows 7。
  • 这在 Windows 7(64 位)上非常适合我。不知道为什么每个人都说没有。我所做的唯一主要修改是去掉 enum 和 switch 语句,并从方法中“返回 false”——我在方法体中进行了所有清理工作。
【解决方案4】:

ZeroKelvin 的答案适用于 Windows 10 x64、.NET 4.6 控制台应用程序。对于那些不需要处理 CtrlType 枚举的人,这里有一个非常简单的方法来挂钩框架的关闭:

class Program
{
    private delegate bool ConsoleCtrlHandlerDelegate(int sig);

    [DllImport("Kernel32")]
    private static extern bool SetConsoleCtrlHandler(ConsoleCtrlHandlerDelegate handler, bool add);

    static ConsoleCtrlHandlerDelegate _consoleCtrlHandler;

    static void Main(string[] args)
    {
        _consoleCtrlHandler += s =>
        {
            //DoCustomShutdownStuff();
            return false;   
        };
        SetConsoleCtrlHandler(_consoleCtrlHandler, true);
    }
}

从处理程序返回 FALSE 告诉框架我们没有“处理”控制信号,并且使用此进程的处理程序列表中的下一个处理程序函数。如果没有任何处理程序返回 TRUE,则调用默认处理程序。

请注意,当用户执行注销或关机时,回调不会被 Windows 调用,而是会立即终止。

【讨论】:

    【解决方案5】:

    Visual Studio 2015 + Windows 10

    • 允许清理
    • 单实例应用
    • 一些镀金

    代码:

    using System;
    using System.Linq;
    using System.Runtime.InteropServices;
    using System.Threading;
    
    namespace YourNamespace
    {
        class Program
        {
            // if you want to allow only one instance otherwise remove the next line
            static Mutex mutex = new Mutex(false, "YOURGUID-YOURGUID-YOURGUID-YO");
    
            static ManualResetEvent run = new ManualResetEvent(true);
    
            [DllImport("Kernel32")]
            private static extern bool SetConsoleCtrlHandler(EventHandler handler, bool add);                
            private delegate bool EventHandler(CtrlType sig);
            static EventHandler exitHandler;
            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 ExitHandler(CtrlType sig)
            {
                Console.WriteLine("Shutting down: " + sig.ToString());            
                run.Reset();
                Thread.Sleep(2000);
                return false; // If the function handles the control signal, it should return TRUE. If it returns FALSE, the next handler function in the list of handlers for this process is used (from MSDN).
            }
    
    
            static void Main(string[] args)
            {
                // if you want to allow only one instance otherwise remove the next 4 lines
                if (!mutex.WaitOne(TimeSpan.FromSeconds(2), false))
                {
                    return; // singleton application already started
                }
    
                exitHandler += new EventHandler(ExitHandler);
                SetConsoleCtrlHandler(exitHandler, true);
    
                try
                {
                    Console.BackgroundColor = ConsoleColor.Gray;
                    Console.ForegroundColor = ConsoleColor.Black;
                    Console.Clear();
                    Console.SetBufferSize(Console.BufferWidth, 1024);
    
                    Console.Title = "Your Console Title - XYZ";
    
                    // start your threads here
                    Thread thread1 = new Thread(new ThreadStart(ThreadFunc1));
                    thread1.Start();
    
                    Thread thread2 = new Thread(new ThreadStart(ThreadFunc2));
                    thread2.IsBackground = true; // a background thread
                    thread2.Start();
    
                    while (run.WaitOne(0))
                    {
                        Thread.Sleep(100);
                    }
    
                    // do thread syncs here signal them the end so they can clean up or use the manual reset event in them or abort them
                    thread1.Abort();
                }
                catch (Exception ex)
                {
                    Console.ForegroundColor = ConsoleColor.Red;
                    Console.Write("fail: ");
                    Console.ForegroundColor = ConsoleColor.Black;
                    Console.WriteLine(ex.Message);
                    if (ex.InnerException != null)
                    {
                        Console.WriteLine("Inner: " + ex.InnerException.Message);
                    }
                }
                finally
                {                
                    // do app cleanup here
    
                    // if you want to allow only one instance otherwise remove the next line
                    mutex.ReleaseMutex();
    
                    // remove this after testing
                    Console.Beep(5000, 100);
                }
            }
    
            public static void ThreadFunc1()
            {
                Console.Write("> ");
                while ((line = Console.ReadLine()) != null)
                {
                    if (line == "command 1")
                    {
    
                    }
                    else if (line == "command 1")
                    {
    
                    }
                    else if (line == "?")
                    {
    
                    }
    
                    Console.Write("> ");
                }
            }
    
    
            public static void ThreadFunc2()
            {
                while (run.WaitOne(0))
                {
                    Thread.Sleep(100);
                }
    
               // do thread cleanup here
                Console.Beep();         
            }
    
        }
    }
    

    【讨论】:

    • 有趣的是,这似乎是最可靠的答案。但是,更改控制台缓冲区大小时要小心:如果缓冲区高度小于窗口高度,程序将在启动时抛出异常。
    【解决方案6】:

    我遇到了类似的问题,只是我的控制台应用程序将无限循环运行,中间有一个抢占式语句。这是我的解决方案:

    class Program
    {
        static int Main(string[] args)
        {
            // Init Code...
            Console.CancelKeyPress += Console_CancelKeyPress;  // Register the function to cancel event
    
            // I do my stuffs
    
            while ( true )
            {
                // Code ....
                SomePreemptiveCall();  // The loop stucks here wating function to return
                // Code ...
            }
            return 0;  // Never comes here, but...
        }
    
        static void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs e)
        {
            Console.WriteLine("Exiting");
            // Termitate what I have to terminate
            Environment.Exit(-1);
        }
    }
    

    【讨论】:

      【解决方案7】:

      对于那些对 VB.net 感兴趣的人。 (我在互联网上搜索并没有找到它的等价物)这里被翻译成vb.net。

          <DllImport("kernel32")> _
          Private Function SetConsoleCtrlHandler(ByVal HandlerRoutine As HandlerDelegate, ByVal Add As Boolean) As Boolean
          End Function
          Private _handler As HandlerDelegate
          Private Delegate Function HandlerDelegate(ByVal dwControlType As ControlEventType) As Boolean
          Private Function ControlHandler(ByVal controlEvent As ControlEventType) As Boolean
              Select Case controlEvent
                  Case ControlEventType.CtrlCEvent, ControlEventType.CtrlCloseEvent
                      Console.WriteLine("Closing...")
                      Return True
                  Case ControlEventType.CtrlLogoffEvent, ControlEventType.CtrlBreakEvent, ControlEventType.CtrlShutdownEvent
                      Console.WriteLine("Shutdown Detected")
                      Return False
              End Select
          End Function
          Sub Main()
              Try
                  _handler = New HandlerDelegate(AddressOf ControlHandler)
                  SetConsoleCtrlHandler(_handler, True)
           .....
      End Sub
      

      【讨论】:

      【解决方案8】:

      也检查一下:

      AppDomain.CurrentDomain.ProcessExit
      

      【讨论】:

      • 这似乎只捕捉返回或环境的退出。退出,它不捕捉 CTRL+C、CTRL+Break 或控制台上的实际关闭按钮。
      • 如果您使用 Console.CancelKeyPress 单独处理 CTRL+C,则在所有 CancelKeyPress 事件处理程序执行后实际引发 ProcessExit 事件。
      • @Konard 我无法让ProcessExit 工作,即使我为CancelKeyPress 注册了一个处理程序。难道你的CancelKeyPress 处理程序和你的ProcessExit 处理程序一样,所以看起来只是ProcessExit 被调用了?
      • @RobinHartmann 我已经重新测试过,我得到了相同的结果 - repl.it/@Konard/CultivatedForsakenQueryoptimizer 这是两个独立的事件,ProcessExit 事件在 CancelKeyPress 事件之后触发。
      • @Konard 感谢您回复我,我也重新测试了,这次我让ProcessExit 工作,即使没有CancelKeyPress。我的意思是ProcessExit 在使用关闭按钮关闭控制台时被调用,即使CancelKeyPress 没有注册。但是你需要CancelKeyPress,如果你想处理CTRL+CCTRL+Break,因为ProcessExit不会被调用。
      【解决方案9】:

      听起来你有线程直接终止应用程序?也许最好让一个线程向主线程发出信号,告诉它应该终止应用程序。

      主线程收到此信号后,可以干净利落地关闭其他线程,最后自行关闭。

      【讨论】:

      • 我必须同意这个答案。强制应用程序退出然后尝试清理不是他要走的路。控制你的应用程序,Noit。不要让它控制你。
      • 我直接生成的线程不一定是唯一可以关闭我的应用程序的东西。 Ctrl-C 和“关闭按钮”是它可以结束的其他方式。 Frank 发布的代码经过轻微修改后非常适合。
      【解决方案10】:

      有用于 WinForms 应用程序的;

      Application.ApplicationExit += CleanupBeforeExit;
      

      对于控制台应用,请尝试

      AppDomain.CurrentDomain.DomainUnload += CleanupBeforeExit;
      

      但我不确定它在什么时候被调用,或者它是否会在当前域中工作。我怀疑不是。

      【讨论】:

      • DomainUnload 的帮助文档说“此事件的 EventHandler 委托可以在卸载应用程序域之前执行任何终止活动。”所以听起来它确实在当前域中工作。但是,它可能无法满足他的需要,因为他的线程可能会保持域正常运行。
      • 这个只处理 CTRL+C 和 CTRL+Close,它不会通过返回、Environment.Exit 或点击关闭按钮来捕获存在。
      • 在 Linux 上使用 Mono 无法为我捕获 CTRL + C。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-12-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-10-31
      相关资源
      最近更新 更多