捕获控制台出口C#


92

我有一个包含很多线程的控制台应用程序。有线程监视某些条件,如果满足,则终止程序。此终止可以随时发生。

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

我想知道是否发生以下事件:

MyConsoleProgram.OnExit += CleanupBeforeExit;

2
我知道这是一个很晚的评论,但是如果您只想“关闭文件和连接”作为清理工作,那么您就不需要这样做。因为Windows在终止期间已经关闭了与进程关联的所有句柄。
Sedat Kapanoglu,2010年

6
^仅那些资源由终止的进程拥有。这是绝对必要的,例如如果你自动在后台隐藏COM应用程序(比如Word或Excel)中,你需要确保你的应用程序退出等之前将其杀死
BrainSlugs83

Answers:


96

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

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

更新资料

对于那些不检查注释的人,似乎该特定解决方案在Windows 7无法很好地(或根本无法)工作。以下线程对此进行了讨论


4
你能用这个取消出口吗?除了关闭时!
ingh.am,2010年

7
这很好用,只有bool Handler()必须return false;(它在代码中不返回任何内容)才能起作用。如果返回true,则Windows会提示“立即终止处理”对话框。= D
Cipi 2010年

3
它看起来像这样的解决方案并与Windows 7不工作了关闭事件,看到social.msdn.microsoft.com/Forums/en/windowscompatibility/thread/...
CharlesB

3
请注意,如果在'Handler'方法中放置一个断点,它将抛出NullReferenceException。在VS2010,Windows 7中签入。–
Maxim

10
在Windows 7(64位)上,这对我非常有用。不知道为什么每个人都说不。我所做的唯一主要修改是摆脱枚举和switch语句,并从该方法“返回false” -我在该方法的主体中进行所有清理。
BrainSlugs83

25

完整的工作示例,适用于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..");
        }
    }
}

2
我在Windows 7上对此进行了测试,所有内容都已注释掉,Handler除了return truewhile和while循环以计数秒。应用程序继续在CTRL-C运行,但与X.闭时在5秒钟后关闭
安东尼奥斯Hadjigeorgalis

很抱歉,但是使用此代码,仅当我按Ctrl + C组合键时,我才能获得“清理完成”的功能,而当我单击“ X”按钮关闭时,则无法完成;在后一种情况下,我只会收到“由于外部CTRL-C导致系统退出,或者进程被杀死或关闭”,但是似乎控制台在执行Handler方法的其余部分之前关闭(使用Win10,.NET Framework 4.6.1)
Giacomo Pirinoli

8

另请检查:

AppDomain.CurrentDomain.ProcessExit

7
这似乎只捕获了return或Environment.Exit的退出,它没有捕获CTRL + C,CTRL + Break或控制台上的实际关闭按钮。
Kit10 2012年

如果使用单独处理CTRL + C,Console.CancelKeyPressProcessExit在所有CancelKeyPress事件处理程序执行后实际上会引发事件。
Konard

5

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

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

4

听起来您有直接终止应用程序的线程?可能最好在主线程中发出一个线程信号以表明应终止该应用程序。

接收到该信号后,主线程可以干净地关闭其他线程,并最终关闭自己。


3
我必须同意这个答案。强迫应用程序退出,然后再尝试清理不是他的方法。控制您的应用程序,Noit。不要让它控制你。
Randolpho

1
由我直接产生的线程不一定是唯一可以关闭我的应用程序的东西。Ctrl-C和“关闭按钮”是结束它的其他方式。弗兰克(Frank)发布的代码经过较小的修改,非常适合。
ZeroKelvin

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不会调用该回调,而是立即终止该回调。


3

有适用于WinForms的应用程序;

Application.ApplicationExit += CleanupBeforeExit;

对于控制台应用程序,请尝试

AppDomain.CurrentDomain.DomainUnload += CleanupBeforeExit;

但是我不确定在什么时候调用它,或者它是否可以在当前域中运行。我怀疑不是。


DomainUnload的帮助文档说:“此事件的EventHandler委托可以在卸载应用程序域之前执行任何终止活动。” 因此,听起来好像它在当前域中确实有效。但是,这可能无法满足他的需要,因为他的线程可能会使域保持运行状态。
罗伯·帕克,2009年

2
这仅处理CTRL + C和CTRL + Close,它无法通过返回Environment.Exit或单击关闭按钮来捕获存在的内容。
Kit10 2012年

在Linux上使用Mono无法为我抓住CTRL +C。
starbeamrainbowlabs

2

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

    }
}

有趣的是,这似乎是最可靠的答案。但是,请注意更改控制台缓冲区的大小:如果缓冲区的高度小于窗口的高度,程序将在启动时引发异常。
约翰·扎布罗斯基

1

查理·B(Charle B)在上面对flq的评论中提到的链接

内心深处说:

如果您链接到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作品,但应用程序暂停在处理任何代码执行之前,反正(我看到断点解雇,右然后应用程序暂停)。
Mike Keskinov '16

但是我找到了适合我的解决方案-我简单地使用了DISABLED关闭按钮。请参阅:stackoverflow.com/questions/6052992/...
麦克Keskinov

0

对于那些对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

上面的解决方案对我不起作用vb.net 4.5框架ControlEventType没有得到解决。我能够将这个想法用作解决方案stackoverflow.com/questions/15317082/…–
一眼看完
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.