Я хотел бы уловить CTRL + C в консольном приложении С#, чтобы я мог выполнить некоторые очистки перед выходом. Каков наилучший способ сделать это?
Как заблокировать ctrl-c в приложении консоли С#
Ответ 1
См. MSDN:
Событие Console.CancelKeyPress
Статья с образцами кода:
Ответ 2
Для этого используется событие Console.CancelKeyPress. Вот как это используется:
public static void Main(string[] args)
{
    Console.CancelKeyPress += delegate {
        // call methods to clean up
    };
    while (true) {}
}
Когда пользователь нажимает Ctrl + C, запускается код в делегате, и программа завершается. Это позволяет выполнять очистку, вызывая необходимые методы. Обратите внимание: никакой код после делегирования не выполняется.
Существуют и другие ситуации, когда это не сократит его. Например, если в настоящее время программа выполняет важные вычисления, которые не могут быть немедленно остановлены. В этом случае правильная стратегия может заключаться в том, чтобы сказать программе выйти после завершения вычисления. Следующий код дает пример того, как это можно реализовать:
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");
    }
}
Разница между этим кодом и первым примером заключается в том, что для параметра e.Cancel установлено значение true, что означает, что выполнение продолжается после делегата. При запуске программа ждет, пока пользователь нажмет Ctrl + C. Когда это произойдет, переменная keepRunning изменит значение, из-за чего цикл while будет завершен. Это способ сделать программу изящной.
Ответ 3
Я хочу добавить к ответ Jonas. Спиннинг на bool приведет к 100% использованию ЦП и отбросит кучу энергии, делая много ничего, ожидая 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();
}
Ответ 4
Вот полный рабочий пример. вставить в пустой проект консоли С#:
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..");
        }
    }
}
Ответ 5
Этот вопрос очень похож на:
Вот как я решил эту проблему, и имел дело с пользователем, поражающим X, а также Ctrl-C. Обратите внимание на использование ManualResetEvents. Это заставит основной поток спать, который освобождает процессор для обработки других потоков, ожидая выхода или очистки. ПРИМЕЧАНИЕ. Необходимо установить TerminationCompletedEvent в конце основного. Невыполнение этого требования приводит к ненужной задержке в завершении из-за выключения времени ОС при убийстве приложения.
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;
        }
    }
}
Ответ 6
 Console.TreatControlCAsInput = true; работал у меня.
