捕获控制台出口 C #

我有一个控制台应用里面有很多线。有些线程监视某些条件,如果条件为真,则终止程序。这种终止随时都可能发生。

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

我想知道是否有这样的活动:

MyConsoleProgram.OnExit += CleanupBeforeExit;
99887 次浏览

There is for WinForms apps;

Application.ApplicationExit += CleanupBeforeExit;

For Console apps, try

AppDomain.CurrentDomain.DomainUnload += CleanupBeforeExit;

But I am not sure at what point that gets called or if it will work from within the current domain. I suspect not.

I am not sure where I found the code on the web, but I found it now in one of my old projects. This will allow you to do cleanup code in your console, e.g. when it is abruptly closed or due to a shutdown...

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

Update

For those not checking the comments it seems that this particular solution does not work well (or at all) on Windows 7. The following thread talks about this

It sounds like you have the threads directly terminating the application? Perhaps it would be better to have a thread signal the main thread to say that the application should be terminated.

On receiving this signal, the main thread can cleanly shutdown the other threads and finally close itself down.

Check also:

AppDomain.CurrentDomain.ProcessExit

For those interested in VB.net. (I searched the internet and couldn't find an equivalent for it) Here it is translated into 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

Full working example, works with ctrl-c, closing the windows with X and kill:

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

I've had a similar problem, just my console App would be running in infinite loop with one preemptive statement on middle. Here is my solution:

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

The link mentioned above by Charle B in comment to flq

Deep down says:

SetConsoleCtrlHandler won't work on windows7 if you link to user32

Some where else in the thread it is suggested to crate a hidden window. So I create a winform and in onload I attached to console and execute original Main. And then SetConsoleCtrlHandle works fine (SetConsoleCtrlHandle is called as suggested by flq)

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

Visual Studio 2015 + Windows 10

  • Allow for cleanup
  • Single instance app
  • Some goldplating

Code:

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


}
}

ZeroKelvin's answer works in Windows 10 x64, .NET 4.6 console app. For those who do not need to deal with the CtrlType enum, here is a really simple way to hook into the framework's shutdown:

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

Returning FALSE from the handler tells the framework that we are not "handling" the control signal, and the next handler function in the list of handlers for this process is used. If none of the handlers returns TRUE, the default handler is called.

Note that when the user performs a logoff or shutdown, the callback is not called by Windows but is instead terminated immediately.