我有一个包含很多线程的控制台应用程序。有线程监视某些条件,如果满足,则终止程序。此终止可以随时发生。
我需要一个可以在程序关闭时触发的事件,以便可以清理所有其他线程并正确关闭所有文件句柄和连接。我不确定.NET框架中是否已经内置了一个,所以我要问自己写的是什么。
我想知道是否发生以下事件:
MyConsoleProgram.OnExit += CleanupBeforeExit;
我有一个包含很多线程的控制台应用程序。有线程监视某些条件,如果满足,则终止程序。此终止可以随时发生。
我需要一个可以在程序关闭时触发的事件,以便可以清理所有其他线程并正确关闭所有文件句柄和连接。我不确定.NET框架中是否已经内置了一个,所以我要问自己写的是什么。
我想知道是否发生以下事件:
MyConsoleProgram.OnExit += CleanupBeforeExit;
Answers:
我不确定在网络上的哪里找到代码,但是现在我在一个旧项目中找到了它。这将允许您在控制台中执行清理代码,例如,突然关闭或由于关机而导致的代码...
[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上无法很好地(或根本无法)工作。以下线程对此进行了讨论
bool Handler()
必须return false;
(它在代码中不返回任何内容)才能起作用。如果返回true,则Windows会提示“立即终止处理”对话框。= D
完整的工作示例,适用于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..");
}
}
}
Handler
除了return true
while和while循环以计数秒。应用程序继续在CTRL-C运行,但与X.闭时在5秒钟后关闭
Handler
方法的其余部分之前关闭(使用Win10,.NET Framework 4.6.1)
另请检查:
AppDomain.CurrentDomain.ProcessExit
Console.CancelKeyPress
则ProcessExit
在所有CancelKeyPress
事件处理程序执行后实际上会引发事件。
我遇到过类似的问题,只是我的控制台应用程序将在无限循环中运行,中间有一个抢占语句。这是我的解决方案:
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);
}
}
听起来您有直接终止应用程序的线程?可能最好在主线程中发出一个线程信号以表明应终止该应用程序。
接收到该信号后,主线程可以干净地关闭其他线程,并最终关闭自己。
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不会调用该回调,而是立即终止该回调。
有适用于WinForms的应用程序;
Application.ApplicationExit += CleanupBeforeExit;
对于控制台应用程序,请尝试
AppDomain.CurrentDomain.DomainUnload += CleanupBeforeExit;
但是我不确定在什么时候调用它,或者它是否可以在当前域中运行。我怀疑不是。
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();
}
}
}
查理·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();
}
AllocConsole
如您的示例所示)显示一些其他信息。问题是,如果用户单击控制台窗口上的(X),则会关闭整个应用程序(所有Windows)。该SetConsoleCtrlHandler
作品,但应用程序暂停在处理任何代码执行之前,反正(我看到断点解雇,右然后应用程序暂停)。
对于那些对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