97

我有一个包含很多线程的控制台应用程序。有些线程监视某些条件并在它们为真时终止程序。这种终止可以随时发生。

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

我想知道是否有类似的事件:

MyConsoleProgram.OnExit += CleanupBeforeExit;
4

10 回答 10

103

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

[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上(或根本)运行良好。以下线程讨论了这个

于 2009-01-23T21:52:03.977 回答
30

完整的工作示例,使用 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..");
        }
    }
}
于 2014-04-10T18:55:48.660 回答
7

还要检查:

AppDomain.CurrentDomain.ProcessExit
于 2009-05-25T15:18:42.487 回答
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);
    }
}
于 2014-09-03T07:07:35.120 回答
4

听起来您有线程直接终止应用程序?也许最好让一个线程向主线程发出信号,说应用程序应该被终止。

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

于 2009-01-23T22:36:29.317 回答
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 调用,而是立即终止。

于 2016-07-08T13:32:13.467 回答
3

有 WinForms 应用程序;

Application.ApplicationExit += CleanupBeforeExit;

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

AppDomain.CurrentDomain.DomainUnload += CleanupBeforeExit;

但我不确定在什么时候调用它,或者它是否会在当前域内工作。我怀疑不是。

于 2009-01-23T21:40:58.803 回答
2

视觉工作室 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();         
        }

    }
}
于 2015-12-31T16:39:24.250 回答
1

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();
}
于 2015-08-21T15:07:50.453 回答
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
于 2011-02-11T21:10:22.597 回答