简体   繁体   English

如何在 C# 控制台应用程序中捕获 Ctrl+C (SIGINT)?

[英]How do I trap Ctrl+C (SIGINT) in a C# console app?

I would like to be able to trap Ctrl + C in a C# console application so that I can carry out some cleanups before exiting.我希望能够在 C# 控制台应用程序中捕获Ctrl + C ,以便在退出之前进行一些清理。 What is the best way of doing this?这样做的最佳方法是什么?

The Console.CancelKeyPress event is used for this. Console.CancelKeyPress事件用于此目的。 This is how it's used:这是它的使用方式:

public static void Main(string[] args)
{
    Console.CancelKeyPress += delegate {
        // call methods to clean up
    };

    while (true) {}
}

When the user presses Ctrl + C the code in the delegate is run and the program exits.当用户按下 Ctrl + C 时,委托中的代码运行并且程序退出。 This allows you to perform cleanup by calling necessary methods.这允许您通过调用必要的方法来执行清理。 Note that no code after the delegate is executed.请注意,委托执行后没有代码。

There are other situations where this won't cut it.在其他情况下,这不会削减它。 For example, if the program is currently performing important calculations that can't be immediately stopped.例如,如果程序当前正在执行无法立即停止的重要计算。 In that case, the correct strategy might be to tell the program to exit after the calculation is complete.在这种情况下,正确的策略可能是在计算完成后告诉程序退出。 The following code gives an example of how this can be implemented:以下代码给出了如何实现的示例:

class MainClass
{
    private static bool keepRunning = true;

    public static void Main(string[] args)
    {
        Console.CancelKeyPress += delegate(object? sender, ConsoleCancelEventArgs e) {
            e.Cancel = true;
            MainClass.keepRunning = false;
        };
        
        while (MainClass.keepRunning) {
            // Do your work in here, in small chunks.
            // If you literally just want to wait until ctrl-c,
            // not doing anything, see the answer using set-reset events.
        }
        Console.WriteLine("exited gracefully");
    }
}

The difference between this code and the first example is that e.Cancel is set to true, which means the execution continues after the delegate.这段代码和第一个例子的不同之处在于, e.Cancel设置为 true,这意味着在委托之后继续执行。 If run, the program waits for the user to press Ctrl + C. When that happens the keepRunning variable changes value which causes the while loop to exit.如果运行,程序会等待用户按下 Ctrl + C。当发生这种情况时, keepRunning变量会更改值,这会导致 while 循环退出。 This is a way to make the program exit gracefully.这是一种让程序优雅退出的方法。

I'd like to add to Jonas' answer .我想补充一下乔纳斯的回答 Spinning on a bool will cause 100% CPU utilization, and waste a bunch of energy doing a lot of nothing while waiting for CTRL + C .bool上旋转将导致 100% 的 CPU 利用率,并在等待CTRL + C时浪费大量精力做很多无所事事。

The better solution is to use a ManualResetEvent to actually "wait" for the CTRL + C :更好的解决方案是使用ManualResetEvent实际“等待” CTRL + C

static void Main(string[] args) {
    var exitEvent = new ManualResetEvent(false);

    Console.CancelKeyPress += (sender, eventArgs) => {
                                  eventArgs.Cancel = true;
                                  exitEvent.Set();
                              };

    var server = new MyServer();     // example
    server.Run();

    exitEvent.WaitOne();
    server.Stop();
}

Here is a complete working example.这是一个完整的工作示例。 paste into empty C# console project:粘贴到空的 C# 控制台项目中:

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

This question is very similar to:这个问题非常类似于:

Capture console exit C# 捕获控制台退出 C#

Here is how I solved this problem, and dealt with the user hitting the X as well as Ctrl-C.这是我解决这个问题的方法,并处理了用户按 X 和 Ctrl-C 的问题。 Notice the use of ManualResetEvents.注意 ManualResetEvents 的使用。 These will cause the main thread to sleep which frees the CPU to process other threads while waiting for either exit, or cleanup.这些将导致主线程休眠,从而释放 CPU 以在等待退出或清理时处理其他线程。 NOTE: It is necessary to set the TerminationCompletedEvent at the end of main.注意:有必要在 main 结束时设置 TerminationCompletedEvent。 Failure to do so causes unnecessary latency in termination due to the OS timing out while killing the application.不这样做会导致由于操作系统在终止应用程序时超时而导致不必要的终止延迟。

namespace CancelSample
{
    using System;
    using System.Threading;
    using System.Runtime.InteropServices;

    internal class Program
    {
        /// <summary>
        /// Adds or removes an application-defined HandlerRoutine function from the list of handler functions for the calling process
        /// </summary>
        /// <param name="handler">A pointer to the application-defined HandlerRoutine function to be added or removed. This parameter can be NULL.</param>
        /// <param name="add">If this parameter is TRUE, the handler is added; if it is FALSE, the handler is removed.</param>
        /// <returns>If the function succeeds, the return value is true.</returns>
        [DllImport("Kernel32")]
        private static extern bool SetConsoleCtrlHandler(ConsoleCloseHandler handler, bool add);

        /// <summary>
        /// The console close handler delegate.
        /// </summary>
        /// <param name="closeReason">
        /// The close reason.
        /// </param>
        /// <returns>
        /// True if cleanup is complete, false to run other registered close handlers.
        /// </returns>
        private delegate bool ConsoleCloseHandler(int closeReason);

        /// <summary>
        ///  Event set when the process is terminated.
        /// </summary>
        private static readonly ManualResetEvent TerminationRequestedEvent;

        /// <summary>
        /// Event set when the process terminates.
        /// </summary>
        private static readonly ManualResetEvent TerminationCompletedEvent;

        /// <summary>
        /// Static constructor
        /// </summary>
        static Program()
        {
            // Do this initialization here to avoid polluting Main() with it
            // also this is a great place to initialize multiple static
            // variables.
            TerminationRequestedEvent = new ManualResetEvent(false);
            TerminationCompletedEvent = new ManualResetEvent(false);
            SetConsoleCtrlHandler(OnConsoleCloseEvent, true);
        }

        /// <summary>
        /// The main console entry point.
        /// </summary>
        /// <param name="args">The commandline arguments.</param>
        private static void Main(string[] args)
        {
            // Wait for the termination event
            while (!TerminationRequestedEvent.WaitOne(0))
            {
                // Something to do while waiting
                Console.WriteLine("Work");
            }

            // Sleep until termination
            TerminationRequestedEvent.WaitOne();

            // Print a message which represents the operation
            Console.WriteLine("Cleanup");

            // Set this to terminate immediately (if not set, the OS will
            // eventually kill the process)
            TerminationCompletedEvent.Set();
        }

        /// <summary>
        /// Method called when the user presses Ctrl-C
        /// </summary>
        /// <param name="reason">The close reason</param>
        private static bool OnConsoleCloseEvent(int reason)
        {
            // Signal termination
            TerminationRequestedEvent.Set();

            // Wait for cleanup
            TerminationCompletedEvent.WaitOne();

            // Don't run other handlers, just exit.
            return true;
        }
    }
}

Console.TreatControlCAsInput = true; has worked for me.对我有用。

I can carry out some cleanups before exiting.我可以在退出之前进行一些清理工作。 What is the best way of doing this Thats is the real goal: trap exit, to make your own stuff.这样做的最佳方法是什么这才是真正的目标:陷阱退出,制作自己的东西。 And neigther answers above not makeing it right.上面的答案都没有正确。 Because, Ctrl+C is just one of many ways to exiting app.因为,Ctrl+C 只是退出应用程序的众多方法之一。

What in dotnet c# is needed for it - so called cancellation token passed to Host.RunAsync(ct) and then, in exit signals traps, for Windows it would be它需要 dotnet c# 中的什么 - 所谓的取消令牌传递给Host.RunAsync(ct)然后,在退出信号陷阱中,对于 Windows,它将是

    private static readonly CancellationTokenSource cts = new CancellationTokenSource();
    public static int Main(string[] args)
    {
        // For gracefull shutdown, trap unload event
        AppDomain.CurrentDomain.ProcessExit += (sender, e) =>
        {
            cts.Cancel();
            exitEvent.Wait();
        };

        Console.CancelKeyPress += (sender, e) =>
        {
            cts.Cancel();
            exitEvent.Wait();
        };

        host.RunAsync(cts);
        Console.WriteLine("Shutting down");
        exitEvent.Set();
        return 0;
     }

... ...

Detect SIGTERM and ctrl+c:检测 SIGTERM 和 ctrl+c:

CancellationTokenSource ctSource = new();
CancellationToken ct = ctSource.Token;

void ExitHandler()
{
    // You can add any arbitrary global clean up
    Console.WriteLine("Exiting...");
    ctSource.Cancel();
}

// Assign exit handler to be called when the process is terminated
// or the user hits CTRL+C
AppDomain.CurrentDomain.ProcessExit += (sender, args) => ExitHandler();
Console.CancelKeyPress += (sender, args) => ExitHandler();

// Then you can use the cancellation token to check for exit:
Console.WriteLine("Ready to gracefully shut down!");
while (!ct.IsCancellationRequested)
{
    Console.WriteLine($"Exit not detected, waiting another 10s.");
    Task.Delay(10000, ct).Wait(ct);
}

就我而言,我将异步 lambda 传递给Console.CancelKeyPress ,但它不起作用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM