Capture de la sortie de la console C #

92

J'ai une application console qui contient pas mal de threads. Il existe des threads qui surveillent certaines conditions et mettent fin au programme si elles sont vraies. Cette résiliation peut survenir à tout moment.

J'ai besoin d'un événement qui peut être déclenché lorsque le programme se ferme afin de pouvoir nettoyer tous les autres threads et fermer correctement tous les descripteurs de fichiers et toutes les connexions. Je ne sais pas s'il y en a déjà un intégré dans le framework .NET, donc je le demande avant d'écrire le mien.

Je me demandais s'il y avait eu un événement du genre:

MyConsoleProgram.OnExit += CleanupBeforeExit;
ZeroKelvin
la source
2
Je sais que c'est un commentaire très tardif, mais vous n'avez pas vraiment besoin de le faire si "fermer les fichiers et les connexions" est la seule chose que vous voulez faire pour le nettoyage. Parce que Windows ferme déjà toutes les poignées associées à un processus lors de l'arrêt.
Sedat Kapanoglu
6
^ Uniquement si ces ressources appartiennent au processus en cours d'arrêt. C'est absolument nécessaire, si, par exemple, vous automatisez une application COM cachée (par exemple, Word ou Excel) en arrière-plan, et que vous devez vous assurer de la tuer avant que votre application ne se
ferme
1
cela a une réponse courte stackoverflow.com/questions/2555292/…
barlop

Réponses:

96

Je ne sais pas où j'ai trouvé le code sur le Web, mais je l'ai trouvé maintenant dans l'un de mes anciens projets. Cela vous permettra de faire du code de nettoyage dans votre console, par exemple lorsqu'elle est brusquement fermée ou en raison d'un arrêt ...

[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)
{
  switch (sig)
  {
      case CtrlType.CTRL_C_EVENT:
      case CtrlType.CTRL_LOGOFF_EVENT:
      case CtrlType.CTRL_SHUTDOWN_EVENT:
      case CtrlType.CTRL_CLOSE_EVENT:
      default:
          return false;
  }
}


static void Main(string[] args)
{
  // Some biolerplate to react to close window event
  _handler += new EventHandler(Handler);
  SetConsoleCtrlHandler(_handler, true);
  ...
}

Mettre à jour

Pour ceux qui ne vérifient pas les commentaires, il semble que cette solution particulière ne fonctionne pas bien (ou pas du tout) sous Windows 7 . Le fil suivant en parle

flq
la source
4
Pouvez-vous l'utiliser pour annuler la sortie? A part quand il s'arrête!
ingh.am
7
Cela fonctionne très bien, seulement bool Handler()doit return false;(il ne renvoie rien dans le code) pour que cela fonctionne. Si elle renvoie true, Windows affiche la boîte de dialogue "Terminer le processus maintenant". = D
Cipi
3
Il semble que cette solution ne fonctionne pas avec Windows 7 pour l'événement d'arrêt, voir social.msdn.microsoft.com/Forums/en/windowscompatibility/thread/…
CharlesB
3
Sachez que si vous mettez un point d'arrêt dans la méthode 'Handler', il lèvera une NullReferenceException. Enregistré dans VS2010, Windows 7.
Maxim
10
Cela a très bien fonctionné pour moi sur Windows 7 (64 bits). Je ne sais pas pourquoi tout le monde dit que non. Les seules modifications majeures que j'ai apportées ont été de me débarrasser de l'instruction enum et switch, et de "renvoyer false" de la méthode - je fais tout mon nettoyage dans le corps de la méthode.
BrainSlugs83
25

Exemple de travail complet, fonctionne avec ctrl-c, ferme les fenêtres avec X et tue:

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 boilerplate 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..");
        }
    }
}
JJ_Coder4Hire
la source
2
J'ai testé cela sur Windows 7 avec tout commenté Handlersauf pour la return trueboucle et une boucle while pour compter les secondes. L'application continue de fonctionner sur ctrl-c mais se ferme après 5 secondes lors de la fermeture avec le X.
Antonios Hadjigeorgalis
Je suis désolé mais en utilisant ce code, je ne peux obtenir "Nettoyage terminé" que si j'appuie sur Ctrl + C, pas si je ferme avec le bouton "X"; dans ce dernier cas, je n'obtiens que "Quitter le système en raison d'un CTRL-C externe, ou d'un processus mort ou d'un arrêt", mais il semble que la console se ferme avant d'exécuter la partie restante de la Handlerméthode {en utilisant Win10, .NET Framework 4.6.1}
Giacomo Pirinoli
8

Vérifiez également:

AppDomain.CurrentDomain.ProcessExit
jmservera
la source
7
Cela semble seulement intercepter les sorties de return ou Environment.Exit, il n'attrape pas CTRL + C, CTRL + Break, ni le bouton de fermeture réel sur la console.
Kit10
Si vous gérez CTRL + C séparément à l'aide de Console.CancelKeyPressalors l' ProcessExitévénement est réellement déclenché après l' CancelKeyPressexécution de tous les gestionnaires d'événements.
Konard
5

J'ai eu un problème similaire, juste mon application console fonctionnerait en boucle infinie avec une déclaration préventive au milieu. Voici ma solution:

class Program
{
    static int Main(string[] args)
    {
        // Init Code...
        Console.CancelKeyPress += Console_CancelKeyPress;  // Register the function to cancel event

        // I do my stuffs

        while ( true )
        {
            // Code ....
            SomePreemptiveCall();  // The loop stucks here wating function to return
            // Code ...
        }
        return 0;  // Never comes here, but...
    }

    static void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs e)
    {
        Console.WriteLine("Exiting");
        // Termitate what I have to terminate
        Environment.Exit(-1);
    }
}
João Portela
la source
4

On dirait que les threads terminent directement l'application? Il serait peut-être préférable qu'un thread signale le thread principal pour dire que l'application doit être terminée.

Sur réception de ce signal, le thread principal peut arrêter proprement les autres threads et finalement se fermer.

Rob
la source
3
Je suis d'accord avec cette réponse. Forcer la fermeture de l'application, puis essayer de nettoyer par la suite n'est pas la bonne voie. Contrôlez votre application, Noit. Ne le laissez pas vous contrôler.
Randolpho
1
Un thread créé par moi directement n'est pas nécessairement la seule chose qui peut fermer mon application. Ctrl-C et le "bouton de fermeture" sont d'autres moyens de se terminer. Le code posté par Frank, après des modifications mineures, s'intègre parfaitement.
ZeroKelvin
4

La réponse de ZeroKelvin fonctionne dans Windows 10 x64, application console .NET 4.6. Pour ceux qui n'ont pas besoin de gérer l'énumération CtrlType, voici un moyen très simple de se connecter à l'arrêt du framework:

class Program
{
    private delegate bool ConsoleCtrlHandlerDelegate(int sig);

    [DllImport("Kernel32")]
    private static extern bool SetConsoleCtrlHandler(ConsoleCtrlHandlerDelegate handler, bool add);

    static ConsoleCtrlHandlerDelegate _consoleCtrlHandler;

    static void Main(string[] args)
    {
        _consoleCtrlHandler += s =>
        {
            //DoCustomShutdownStuff();
            return false;   
        };
        SetConsoleCtrlHandler(_consoleCtrlHandler, true);
    }
}

Renvoyer FALSE depuis le gestionnaire indique au framework que nous ne «gérons» pas le signal de contrôle, et la fonction de gestionnaire suivante dans la liste des gestionnaires pour ce processus est utilisée. Si aucun des gestionnaires ne renvoie TRUE, le gestionnaire par défaut est appelé.

Notez que lorsque l'utilisateur effectue une déconnexion ou un arrêt, le rappel n'est pas appelé par Windows, mais se termine immédiatement.

BCA
la source
3

Il existe pour les applications WinForms;

Application.ApplicationExit += CleanupBeforeExit;

Pour les applications de la console, essayez

AppDomain.CurrentDomain.DomainUnload += CleanupBeforeExit;

Mais je ne sais pas à quel moment cela sera appelé ou si cela fonctionnera à partir du domaine actuel. Je suppose que non.

Rob Prouse
la source
La documentation d'aide pour DomainUnload indique que "Le délégué EventHandler pour cet événement peut effectuer toutes les activités de résiliation avant le déchargement du domaine d'application." Il semble donc que cela fonctionne dans le domaine actuel. Cependant, cela peut ne pas répondre à ses besoins car ses threads peuvent maintenir le domaine en place.
Rob Parker
2
Cela ne gère que CTRL + C et CTRL + Fermer, il ne détecte pas l'existence via le retour, Environment.Exit ni en cliquant sur le bouton de fermeture.
Kit10
N'attrape pas CTRL + C pour moi avec Mono sur Linux.
starbeamrainbowlabs
2

Visual Studio 2015 et Windows 10

  • Autoriser le nettoyage
  • Application à instance unique
  • Certains plaqués or

Code:

using System;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;

namespace YourNamespace
{
    class Program
    {
        // if you want to allow only one instance otherwise remove the next line
        static Mutex mutex = new Mutex(false, "YOURGUID-YOURGUID-YOURGUID-YO");

        static ManualResetEvent run = new ManualResetEvent(true);

        [DllImport("Kernel32")]
        private static extern bool SetConsoleCtrlHandler(EventHandler handler, bool add);                
        private delegate bool EventHandler(CtrlType sig);
        static EventHandler exitHandler;
        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 ExitHandler(CtrlType sig)
        {
            Console.WriteLine("Shutting down: " + sig.ToString());            
            run.Reset();
            Thread.Sleep(2000);
            return false; // If the function handles the control signal, it should return TRUE. If it returns FALSE, the next handler function in the list of handlers for this process is used (from MSDN).
        }


        static void Main(string[] args)
        {
            // if you want to allow only one instance otherwise remove the next 4 lines
            if (!mutex.WaitOne(TimeSpan.FromSeconds(2), false))
            {
                return; // singleton application already started
            }

            exitHandler += new EventHandler(ExitHandler);
            SetConsoleCtrlHandler(exitHandler, true);

            try
            {
                Console.BackgroundColor = ConsoleColor.Gray;
                Console.ForegroundColor = ConsoleColor.Black;
                Console.Clear();
                Console.SetBufferSize(Console.BufferWidth, 1024);

                Console.Title = "Your Console Title - XYZ";

                // start your threads here
                Thread thread1 = new Thread(new ThreadStart(ThreadFunc1));
                thread1.Start();

                Thread thread2 = new Thread(new ThreadStart(ThreadFunc2));
                thread2.IsBackground = true; // a background thread
                thread2.Start();

                while (run.WaitOne(0))
                {
                    Thread.Sleep(100);
                }

                // do thread syncs here signal them the end so they can clean up or use the manual reset event in them or abort them
                thread1.Abort();
            }
            catch (Exception ex)
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.Write("fail: ");
                Console.ForegroundColor = ConsoleColor.Black;
                Console.WriteLine(ex.Message);
                if (ex.InnerException != null)
                {
                    Console.WriteLine("Inner: " + ex.InnerException.Message);
                }
            }
            finally
            {                
                // do app cleanup here

                // if you want to allow only one instance otherwise remove the next line
                mutex.ReleaseMutex();

                // remove this after testing
                Console.Beep(5000, 100);
            }
        }

        public static void ThreadFunc1()
        {
            Console.Write("> ");
            while ((line = Console.ReadLine()) != null)
            {
                if (line == "command 1")
                {

                }
                else if (line == "command 1")
                {

                }
                else if (line == "?")
                {

                }

                Console.Write("> ");
            }
        }


        public static void ThreadFunc2()
        {
            while (run.WaitOne(0))
            {
                Thread.Sleep(100);
            }

           // do thread cleanup here
            Console.Beep();         
        }

    }
}
AJBauer
la source
Il est intéressant de noter que cela semble être la réponse la plus robuste. Attention cependant à changer la taille du buffer de votre console: si la hauteur du buffer est inférieure à la hauteur de la fenêtre, le programme lancera une exception au démarrage.
John Zabroski
1

Le lien mentionné ci-dessus par Charle B en commentaire à flq

Au fond, dit:

SetConsoleCtrlHandler ne fonctionnera pas sous Windows7 si vous créez un lien vers user32

Quelque part ailleurs dans le fil, il est suggéré de créer une fenêtre cachée. Donc je crée un winform et en onload je me suis attaché à la console et j'exécute Main originale. Et puis SetConsoleCtrlHandle fonctionne correctement (SetConsoleCtrlHandle est appelé comme suggéré par flq)

public partial class App3DummyForm : Form
{
    private readonly string[] _args;

    public App3DummyForm(string[] args)
    {
        _args = args;
        InitializeComponent();
    }

    private void App3DummyForm_Load(object sender, EventArgs e)
    {
        AllocConsole();
        App3.Program.OriginalMain(_args);
    }

    [DllImport("kernel32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool AllocConsole();
}
Jens
la source
En fait, cela ne fonctionne pas. J'ai une application WFP multi-fenêtres et j'utilise la console ( AllocConsolecomme dans votre exemple) pour afficher des informations supplémentaires. Le problème est que toute l'application (toutes les fenêtres) se ferme si l'utilisateur clique sur le (X) dans la fenêtre de la console. Les SetConsoleCtrlHandlertravaux, mais les arrêts d'application de toute façon avant que le code dans le gestionnaire exécuté (je vois des points d' arrêt et ont tiré à droite , puis les arrêts d'application).
Mike Keskinov
Mais j'ai trouvé une solution qui fonctionne pour moi - je simple , HANDICAPÉS bouton Fermer. Voir: stackoverflow.com/questions/6052992/…
Mike Keskinov
0

Pour ceux qui s'intéressent à VB.net. (J'ai cherché sur Internet et je n'ai pas trouvé d'équivalent) Ici, il est traduit en vb.net.

    <DllImport("kernel32")> _
    Private Function SetConsoleCtrlHandler(ByVal HandlerRoutine As HandlerDelegate, ByVal Add As Boolean) As Boolean
    End Function
    Private _handler As HandlerDelegate
    Private Delegate Function HandlerDelegate(ByVal dwControlType As ControlEventType) As Boolean
    Private Function ControlHandler(ByVal controlEvent As ControlEventType) As Boolean
        Select Case controlEvent
            Case ControlEventType.CtrlCEvent, ControlEventType.CtrlCloseEvent
                Console.WriteLine("Closing...")
                Return True
            Case ControlEventType.CtrlLogoffEvent, ControlEventType.CtrlBreakEvent, ControlEventType.CtrlShutdownEvent
                Console.WriteLine("Shutdown Detected")
                Return False
        End Select
    End Function
    Sub Main()
        Try
            _handler = New HandlerDelegate(AddressOf ControlHandler)
            SetConsoleCtrlHandler(_handler, True)
     .....
End Sub
dko
la source
La solution ci-dessus ne fonctionne pas pour moi vb.net 4.5 framework ControlEventType ne se résout pas. J'ai pu utiliser cette idée comme solution stackoverflow.com/questions/15317082/…
glant