web-dev-qa-db-fra.com

Comment intercepter ctrl-c (SIGINT) dans une application console C #

J'aimerais pouvoir piéger CTRL+C dans une application console C # afin que je puisse effectuer certains nettoyages avant de quitter. Quelle est la meilleure façon de faire cela?

202
Nick Randell
113
aku

L'événement Console.CancelKeyPress est utilisé pour cela. Voici comment il est utilisé:

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

    while (true) {}
}

Lorsque l'utilisateur appuie sur Ctrl + C, le code du délégué est exécuté et le programme se ferme. Cela vous permet d'effectuer un nettoyage en appelant les méthodes nécessaires. Notez qu'aucun code après l'exécution du délégué.

Il existe d'autres situations où cela ne suffira pas. Par exemple, si le programme effectue actuellement des calculs importants qui ne peuvent pas être arrêtés immédiatement. Dans ce cas, la stratégie appropriée pourrait être d'indiquer au programme de se terminer une fois le calcul terminé. Le code suivant donne un exemple de la façon dont cela peut être implémenté:

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

La différence entre ce code et le premier exemple est que e.Cancel est défini sur true, ce qui signifie que l'exécution se poursuit après le délégué. S'il est exécuté, le programme attend que l'utilisateur appuie sur Ctrl + C. Lorsque cela se produit, la variable keepRunning change de valeur, ce qui provoque la fermeture de la boucle while. C'est un moyen de faire sortir le programme correctement.

207
Jonas

J'aimerais ajouter à réponse de Jonas . Tourner sur un bool entraînera une utilisation de 100% de la CPU, et gaspillera beaucoup d'énergie à ne rien faire en attendant CTRL+C.

La meilleure solution consiste à utiliser un ManualResetEvent pour réellement "attendre" la 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();
}
88
Jonathon Reinhart

Voici un exemple de travail complet. coller dans un projet de console C # vide:

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

Cette question est très similaire à:

Capture console C #

Voici comment j'ai résolu ce problème, et traité avec l'utilisateur frappant le X ainsi que Ctrl-C. Notez l'utilisation de ManualResetEvents. Celles-ci vont mettre le thread principal en veille, ce qui permet au processeur de traiter d'autres threads en attendant la sortie ou le nettoyage. REMARQUE: Il est nécessaire de définir TerminationCompletedEvent à la fin de main. Si vous ne le faites pas, cela entraîne une latence inutile dans la terminaison en raison de l'expiration du système d'exploitation tout en détruisant l'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;
        }
    }
}
6
Paul

Console.TreatControlCAsInput = true; a travaillé pour moi.

3
hallo