2009-01-23 148 views
78

我有一个包含相当多线程的控制台应用程序。有线程监视某些条件并在程序正确时终止程序。这种终止可以随时发生。捕获控制台出口C#

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

我在想,如果有沿行的事件:

MyConsoleProgram.OnExit += CleanupBeforeExit; 
+2

我知道这是一个非常晚的评论,但如果“关闭文件和连接”是你想做清理的唯一事情,你并不需要这么做。因为Windows在终止过程中已关闭与进程关联的所有句柄。 – 2010-03-23 08:39:43

+3

^只有当这些资源属于正在终止的进程。这是绝对必要的,例如,如果您在后台自动化隐藏的COM应用程序(例如Word或Excel),并且您需要确保在您的应用程序退出之前将其杀死,等等。 – BrainSlugs83 2013-11-17 07:58:01

+1

这有一个短寻找答案http://stackoverflow.com/questions/2555292/how-to-run-code-before-program-exit – barlop 2016-03-18 14:49:28

回答

83

我不知道我在网上找到了哪些代码,但现在我在其中一个旧项目中找到了它。这将允许您在控制台中执行清理代码,例如当它被突然关闭或因关机...

[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。以下thread谈论这个

+3

你可以用它来取消退出吗?除了它关闭的时候! – 2010-03-04 21:06:02

+0

詹姆斯,我不知道,但处理程序签名返回一个布尔,所以我会尝试什么发生时,当你返回true/false – flq 2010-03-05 20:26:00

+6

这很好,只有`布尔Handler()`必须`返回false;`(它返回没有代码),所以它会工作。如果它返回true,则windows会提示“立即终止进程”对话框。 = D – Cipi 2010-04-07 09:16:15

3

没有为WinForms的应用;

Application.ApplicationExit += CleanupBeforeExit; 

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

AppDomain.CurrentDomain.DomainUnload += CleanupBeforeExit; 

但我不知道在什么时候被调用或是否会从当前域范围内工作。我怀疑不是。

+0

DomainUnload的帮助文档说:“该事件的EventHandler委托可以在应用程序域之前执行任何终止活动被卸载。“所以它听起来像是在当前的域中起作用。但是,这可能无法满足他的需求,因为他的线程可能会使域名保持不变。 – 2009-03-23 16:58:01

+1

这只能处理CTRL + C和CTRL + Close,它不会通过返回,Environment.Exit或单击关闭按钮来捕获。 – Kit10 2012-08-31 18:23:46

3

这听起来像你有线程直接终止应用程序?也许最好有一个线程信号通知主线程说应用程序应该被终止。

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

7

检查也:

AppDomain.CurrentDomain.ProcessExit 
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 
19

全部工作的例子,工程用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 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

我也有过类似的问题,只是我的控制台应用程序,会在无限循环中运行中间有一个先发制人的声明。这里是我的解决方案:

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

在评论由查理上述B提到的link到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(); 
} 
0

的Visual Studio 2015年+的Windows 10

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

代码:

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

    } 
} 
0

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