Comment piéger ctrl-c (SIGINT) dans une application console C #
222
Je voudrais pouvoir piéger CTRL+ Cdans une application console C # afin de pouvoir effectuer quelques nettoyages avant de quitter. Quelle est la meilleure façon de procéder?
En fait, cet article recommande P / Invoke et CancelKeyPressn'est mentionné que brièvement dans les commentaires. Un bon article est codeneverwritten.com/2006/10/…
bzlm
Cela fonctionne mais il ne piège pas la fermeture de la fenêtre avec le X. Voir ma solution complète ci-dessous. fonctionne aussi avec kill
JJ_Coder4Hire
1
J'ai constaté que cette Console.CancelKeyPress cessera de fonctionner si la console est fermée. Exécuter une application sous mono / linux avec systemd, ou si l'application est exécutée en tant que "mono myapp.exe </ dev / null", un SIGINT sera envoyé au gestionnaire de signal par défaut et tuera instantanément l'application. Les utilisateurs de Linux voudront peut-être voir stackoverflow.com/questions/6546509/…
@aku n'y a-t-il pas suffisamment de temps pour répondre au SIGINT avant que le processus ne soit brutalement interrompu? Je ne le trouve nulle part et j'essaie de me rappeler où je l'ai lu.
John Zabroski
224
L' événement Console.CancelKeyPress est utilisé pour cela. Voici comment il est utilisé:
publicstaticvoidMain(string[] args){Console.CancelKeyPress+=delegate{// call methods to clean up};while(true){}}
Lorsque l'utilisateur appuie sur Ctrl + C, le code dans le 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 y a d'autres situations où cela ne suffira pas. Par exemple, si le programme effectue actuellement des calculs importants qui ne peuvent pas être immédiatement arrêtés. Dans ce cas, la bonne stratégie pourrait être de dire au programme de quitter une fois le calcul terminé. Le code suivant donne un exemple de la façon dont cela peut être implémenté:
classMainClass{privatestaticbool keepRunning =true;publicstaticvoidMain(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 qu'il e.Cancelest 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 keepRunningvariable change de valeur, ce qui provoque la sortie de la boucle while. C'est une façon de faire sortir le programme avec élégance.
keepRunningpeut avoir besoin d'être marqué volatile. Sinon, le thread principal peut le mettre en cache sur un registre CPU et ne remarquera pas la modification de la valeur lorsque le délégué est exécuté.
cdhowie
Cela fonctionne mais il ne piège pas la fermeture de la fenêtre avec le X. Voir ma solution complète ci-dessous. fonctionne aussi avec kill.
JJ_Coder4Hire
13
Doit être modifié pour utiliser un ManualResetEventplutôt que de tourner sur un bool.
Mizipzor
Une petite mise en garde pour quiconque exécute des trucs en cours d'exécution dans Git-Bash, MSYS2 ou CygWin: Vous devrez exécuter dotnet via winpty pour que cela fonctionne (donc, winpty dotnet run). Sinon, le délégué ne sera jamais exécuté.
Samuel Lampa
Attention, l'événement pour CancelKeyPress est géré sur un thread de pool de threads, ce qui n'est pas immédiatement évident: docs.microsoft.com/en-us/dotnet/api/…
Sam Rueby
100
Je voudrais ajouter à la réponse de Jonas . Tourner sur un boolentraînera une utilisation de 100% du processeur et gaspillera beaucoup d'énergie sans rien faire en attendant CTRL+ C.
La meilleure solution consiste à utiliser un ManualResetEventpour "attendre" le CTRL+ C:
staticvoidMain(string[] args){var exitEvent =newManualResetEvent(false);Console.CancelKeyPress+=(sender, eventArgs)=>{
eventArgs.Cancel=true;
exitEvent.Set();};var server =newMyServer();// example
server.Run();
exitEvent.WaitOne();
server.Stop();}
Je pense que le fait est que vous feriez tout le travail à l'intérieur de la boucle while, et que Ctrl + C ne se cassera pas au milieu de l'itération while; il terminera cette itération avant de se déclencher.
pkr298
2
@ pkr298 - Dommage que les gens ne votent pas votre commentaire car c'est tout à fait vrai. Je vais éditer Jonas sa réponse pour clarifier les gens de penser comme Jonathon (ce qui n'est pas mauvais en soi mais pas comme Jonas voulait dire sa réponse)
M. Mimpen
Mettez à jour la réponse existante avec cette amélioration.
Mizipzor
27
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{publicclassProgram{staticbool exitSystem =false;#region Trap application termination[DllImport("Kernel32")]privatestaticexternboolSetConsoleCtrlHandler(EventHandler handler,booladd);privatedelegateboolEventHandler(CtrlType sig);staticEventHandler _handler;enumCtrlType{
CTRL_C_EVENT =0,
CTRL_BREAK_EVENT =1,
CTRL_CLOSE_EVENT =2,
CTRL_LOGOFF_EVENT =5,
CTRL_SHUTDOWN_EVENT =6}privatestaticboolHandler(CtrlType sig){Console.WriteLine("Exiting system due to external CTRL-C, or process kill, or shutdown");//do your cleanup hereThread.Sleep(5000);//simulate some cleanup delayConsole.WriteLine("Cleanup complete");//allow main to run off
exitSystem =true;//shutdown right away so there are no lingering threadsEnvironment.Exit(-1);returntrue;}#endregionstaticvoidMain(string[] args){// Some biolerplate to react to close window event, CTRL-C, kill, etc
_handler +=newEventHandler(Handler);SetConsoleCtrlHandler(_handler,true);//start your multi threaded program hereProgram p =newProgram();
p.Start();//hold the console so it doesn’t run off the endwhile(!exitSystem){Thread.Sleep(500);}}publicvoidStart(){// start a thread and start doing some processingConsole.WriteLine("Thread started, processing..");}}}
J'ai juste ajouté ceci parce que c'est la seule réponse qui répond à une réponse complète. Celui-ci est complet et vous permet d'exécuter le programme sous le planificateur de tâches sans intervention de l'utilisateur. Vous avez toujours la possibilité de nettoyer. Utilisez NLOG dans votre projet et vous avez quelque chose de gérable. Je me demande si ça va compiler en .NET Core 2 ou 3.
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. Cela entraînera la mise en veille du thread principal, ce qui libère le processeur pour traiter d'autres threads en attendant la sortie ou le nettoyage. REMARQUE: Il est nécessaire de définir TerminationCompletedEvent à la fin de main. Ne pas le faire entraîne une latence inutile dans la terminaison en raison de l'expiration du système d'exploitation tout en tuant l'application.
namespace CancelSample{
using System;
using System.Threading;
using System.Runtime.InteropServices;internalclassProgram{/// <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")]privatestaticexternboolSetConsoleCtrlHandler(ConsoleCloseHandler handler,booladd);/// <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>privatedelegateboolConsoleCloseHandler(int closeReason);/// <summary>/// Event set when the process is terminated./// </summary>privatestaticreadonlyManualResetEventTerminationRequestedEvent;/// <summary>/// Event set when the process terminates./// </summary>privatestaticreadonlyManualResetEventTerminationCompletedEvent;/// <summary>/// Static constructor/// </summary>staticProgram(){// Do this initialization here to avoid polluting Main() with it// also this is a great place to initialize multiple static// variables.TerminationRequestedEvent=newManualResetEvent(false);TerminationCompletedEvent=newManualResetEvent(false);SetConsoleCtrlHandler(OnConsoleCloseEvent,true);}/// <summary>/// The main console entry point./// </summary>/// <param name="args">The commandline arguments.</param>privatestaticvoidMain(string[] args){// Wait for the termination eventwhile(!TerminationRequestedEvent.WaitOne(0)){// Something to do while waitingConsole.WriteLine("Work");}// Sleep until terminationTerminationRequestedEvent.WaitOne();// Print a message which represents the operationConsole.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>privatestaticboolOnConsoleCloseEvent(int reason){// Signal terminationTerminationRequestedEvent.Set();// Wait for cleanupTerminationCompletedEvent.WaitOne();// Don't run other handlers, just exit.returntrue;}}}
Cela peut obliger ReadLine à nécessiter deux pressions sur Entrée pour chaque entrée.
Grault
0
Je peux effectuer quelques nettoyages avant de quitter. Quelle est la meilleure façon de le faire? C'est le véritable objectif: sortir du piège, faire vos propres trucs. Et les réponses neigther ci-dessus ne le font pas bien. Parce que Ctrl + C n'est qu'une des nombreuses façons de quitter l'application.
Ce qui est nécessaire dans dotnet c # - ce que l'on appelle un jeton d'annulation transmis à Host.RunAsync(ct)puis dans les interruptions de signaux de sortie, pour Windows, il serait
CancelKeyPress
n'est mentionné que brièvement dans les commentaires. Un bon article est codeneverwritten.com/2006/10/…L' événement Console.CancelKeyPress est utilisé pour cela. Voici comment il est utilisé:
Lorsque l'utilisateur appuie sur Ctrl + C, le code dans le 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 y a d'autres situations où cela ne suffira pas. Par exemple, si le programme effectue actuellement des calculs importants qui ne peuvent pas être immédiatement arrêtés. Dans ce cas, la bonne stratégie pourrait être de dire au programme de quitter une fois le calcul terminé. Le code suivant donne un exemple de la façon dont cela peut être implémenté:
La différence entre ce code et le premier exemple est qu'il
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, lakeepRunning
variable change de valeur, ce qui provoque la sortie de la boucle while. C'est une façon de faire sortir le programme avec élégance.la source
keepRunning
peut avoir besoin d'être marquévolatile
. Sinon, le thread principal peut le mettre en cache sur un registre CPU et ne remarquera pas la modification de la valeur lorsque le délégué est exécuté.ManualResetEvent
plutôt que de tourner sur unbool
.winpty dotnet run
). Sinon, le délégué ne sera jamais exécuté.Je voudrais ajouter à la réponse de Jonas . Tourner sur un
bool
entraînera une utilisation de 100% du processeur et gaspillera beaucoup d'énergie sans rien faire en attendant CTRL+ C.La meilleure solution consiste à utiliser un
ManualResetEvent
pour "attendre" le CTRL+ C:la source
Voici un exemple de travail complet. coller dans un projet de console C # vide:
la source
Cette question est très similaire à:
Quitter la console de capture 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. Cela entraînera la mise en veille du thread principal, ce qui libère le processeur pour traiter d'autres threads en attendant la sortie ou le nettoyage. REMARQUE: Il est nécessaire de définir TerminationCompletedEvent à la fin de main. Ne pas le faire entraîne une latence inutile dans la terminaison en raison de l'expiration du système d'exploitation tout en tuant l'application.
la source
Console.TreatControlCAsInput = true;
a travaillé pour moi.la source
Ce qui est nécessaire dans dotnet c # - ce que l'on appelle un jeton d'annulation transmis à
Host.RunAsync(ct)
puis dans les interruptions de signaux de sortie, pour Windows, il serait...
la source