Як відключити ctrl-c (SIGINT) у консольному додатку C #


221

Я хотів би мати можливість потрапити в пастку CTRL+ Cу консольній програмі C #, щоб я міг здійснити кілька очищень перед виходом. Який найкращий спосіб зробити це?

Відповіді:


126

Дивіться MSDN:

Console.CancelKeyPress подія

Стаття із зразками коду:

Ctrl-C і консольний додаток .NET


6
Насправді ця стаття рекомендує P / Invoke, і CancelKeyPressвона лише коротко згадується в коментарях. Хороша стаття - codeneverwritten.com/2006/10/…
bzlm

Це працює, але це не захоплює закриття вікна з X. Дивіться моє повне рішення нижче. також працює з kill
JJ_Coder4Hire

1
Я виявив, що Console.CancelKeyPress перестане працювати, якщо консоль закрита. Запустивши додаток під mono / linux з systemd, або якщо додаток запускається як "mono myapp.exe </ dev / null", SIGINT буде надісланий до обробника сигналу за замовчуванням і миттєво знищить додаток. Користувачі Linux можуть хотіти бачити stackoverflow.com/questions/6546509 / ...
tekHedd

2
Нерозривне посилання на коментар @ bzlm: web.archive.org/web/20110424085511/http://…
Іен Кемп

@aku Чи не потрібно багато часу, щоб відповісти на SIGINT, перш ніж процес грубо перерветься? Я ніде не можу його знайти, і я намагаюся згадати, де я її читав.
Іван Заброський

224

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він встановлений у вірно, що означає, що виконання триває після делегата. Якщо запустити, програма чекає, коли користувач натисне Ctrl + C. Коли це станеться, keepRunningзмінна змінює значення, яке призводить до виходу циклу while. Це спосіб виграшно вийти з програми.


21
keepRunningможе знадобитися позначення volatile. Основний потік може кешувати його в регістрі процесора інакше, і не помітить зміни значення при виконанні делегата.
cdhowie

Це працює, але це не захоплює закриття вікна з X. Дивіться моє повне рішення нижче. працює і з kill.
JJ_Coder4Hire

13
Слід змінити на використання, ManualResetEventа не на спінінг на bool.
Мізіпзор

Невеликий застереження для всіх, хто працює з запущеними елементами в Git-Bash, MSYS2 або CygWin: Вам потрібно буде запустити dotnet через winpty, щоб це працювало (Отже, winpty dotnet run). В іншому випадку делегат ніколи не буде запущений.
Самуель Лампа

Спостерігайте - подія для CancelKeyPress проводиться в потоці пулу потоків, що не відразу очевидно: docs.microsoft.com/en-us/dotnet/api/…
Сем Рубі

100

Я хотів би додати відповідь Йонаса . Спінінг за замовчуванням 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();
}

28
Я думаю, що справа в тому, що ви зробите всю роботу всередині циклу while, і натискання Ctrl + C не зламається в середині ітерації в той час; вона завершить цю ітерацію, перш ніж спалахнути.
pkr298

2
@ pkr298 - Дуже погані люди не голосують за ваш коментар, оскільки це повністю правда. Я відредагую його відповідь Йонаса, щоб пояснити людям думки, як це робив Джонатан (що не є по суті поганим, але не так, як Йонас мав на увазі його відповідь)
М. Мімпен

Оновіть існуючу відповідь за допомогою цього вдосконалення.
Мізіпзор

27

Ось повний робочий приклад. вставити в порожній проект консолі 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..");
        }
    }
}

8
цей p / посилається, таким чином, не є кросплатформою
knocte

Я просто додав це, тому що це єдина відповідь, яка відповідає на повну відповідь. Цей ретельний і дозволяє запускати програму під планувальником завдань без участі користувача. Ви все одно отримаєте шанс на очищення. Використовуйте NLOG у своєму проекті, і у вас є щось кероване. Цікаво, чи
збиратиметься

7

Це питання дуже схоже на:

Захоплення консолі виходу C #

Ось, як я вирішив цю проблему і вирішив, що користувач потрапив на X, а також на Ctrl-C. Зверніть увагу на використання ManualResetEvents. Це призведе до сну основної нитки, яка звільняє ЦП обробляти інші потоки під час очікування виходу або очищення. ПРИМІТКА: ТермінCompletedEvent необхідно встановити в кінці основного. Якщо цього не зробити, це призводить до зайвої затримки в припиненні через час вичерпання ОС під час вбивства програми.

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

3

Console.TreatControlCAsInput = true; працював на мене.


2
Це може призвести до того, що ReadLine вимагає двох натискань Enter для кожного вводу.
Грауль

0

Я можу здійснити кілька прибирань перед виходом. Який найкращий спосіб зробити це - справжня мета: вийти з пастки, щоб зробити свої речі. І більш відповіді вище, не роблячи це правильно. Тому що Ctrl + C - лише один із багатьох способів виходу із програми.

Що в 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;
     }

...

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.