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?
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?
Réponses:
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 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é:
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 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, la keepRunning
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.
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 un bool
.
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:
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();
}
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..");
}
}
}
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.
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;
}
}
}
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
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;
}
...
CancelKeyPress
n'est mentionné que brièvement dans les commentaires. Un bon article est codeneverwritten.com/2006/10/…