2009-01-23 121 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調用,而是立即終止。