Захватить консоль выхода C # - PullRequest
85 голосов
/ 24 января 2009

У меня есть консольное приложение, которое содержит довольно много потоков. Существуют потоки, которые отслеживают определенные условия и завершают программу, если они верны. Это прекращение может произойти в любое время.

Мне нужно событие, которое может быть вызвано при закрытии программы, чтобы я мог очистить все остальные потоки и правильно закрыть все файловые дескрипторы и соединения. Я не уверен, есть ли уже встроенный в .NET Framework, поэтому я спрашиваю, прежде чем написать свой собственный.

Мне было интересно, произошло ли событие в духе:

MyConsoleProgram.OnExit += CleanupBeforeExit;

Ответы [ 10 ]

92 голосов
/ 24 января 2009

Я не уверен, где я нашел код в Интернете, но я нашел его сейчас в одном из моих старых проектов. Это позволит вам выполнить код очистки в вашей консоли, например, когда он внезапно закрыт или из-за остановки ...

[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);
  ...
}

Обновление

Для тех, кто не проверяет комментарии, кажется, что это конкретное решение не работает хорошо (или вообще) на Windows 7 Следующая тема говорит об этом

24 голосов
/ 10 апреля 2014

Полный рабочий пример, работает с Ctrl-C, закрывает окна с помощью X и убивает:

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..");
        }
    }
}
7 голосов
/ 25 мая 2009

Проверьте также:

AppDomain.CurrentDomain.ProcessExit
3 голосов
/ 03 сентября 2014

У меня была похожая проблема, просто мое консольное приложение работало бы в бесконечном цикле с одним вытесняющим оператором в середине. Вот мое решение:

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);
    }
}
3 голосов
/ 24 января 2009

Похоже, у вас есть темы, непосредственно завершающие приложение? Возможно, было бы лучше, чтобы поток сигнализировал основному потоку о том, что приложение должно быть прекращено.

Получив этот сигнал, основной поток может полностью завершить работу других потоков и, наконец, закрыть себя.

3 голосов
/ 24 января 2009

Есть для приложений WinForms;

Application.ApplicationExit += CleanupBeforeExit;

Для консольных приложений попробуйте

AppDomain.CurrentDomain.DomainUnload += CleanupBeforeExit;

Но я не уверен, в какой момент он вызывается или будет работать из текущего домена. Я подозреваю, что нет.

2 голосов
/ 08 июля 2016

Ответ ZeroKelvin работает в консольном приложении Windows 10 x64, .NET 4.6. Для тех, кому не нужно иметь дело с перечислением CtrlType, вот действительно простой способ подключиться к завершению работы фреймворка:

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

Возвращение FALSE из обработчика говорит платформе, что мы не "обрабатываем" управляющий сигнал, и используется следующая функция обработчика в списке обработчиков для этого процесса. Если ни один из обработчиков не возвращает TRUE, вызывается обработчик по умолчанию.

Обратите внимание, что когда пользователь выполняет выход из системы или завершение работы, обратный вызов не вызывается Windows, а вместо этого немедленно завершается.

1 голос
/ 21 августа 2015

Ссылка , упомянутая выше Шарлем Б в комментарии к flq

В глубине души говорит:

SetConsoleCtrlHandler не будет работать на windows7, если вы ссылаетесь на user32

Где-то еще в теме предлагается создать скрытое окно. Поэтому я создаю winform и в onload подключаюсь к консоли и выполняю оригинальный Main. И тогда SetConsoleCtrlHandle работает нормально (SetConsoleCtrlHandle вызывается как предложено 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();
}
0 голосов
/ 31 декабря 2015

Visual Studio 2015 + Windows 10

  • Разрешить для очистки
  • Приложение с одним экземпляром
  • немного позолоты

Код:

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

    }
}
0 голосов
/ 12 февраля 2011

Для тех, кто интересуется VB.net. (Я искал в интернете и не нашел для него эквивалента) Вот он переведен на 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
...