Выход консоли захвата C #

93

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

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

Мне было интересно, было ли событие вроде:

MyConsoleProgram.OnExit += CleanupBeforeExit;
Ноль Кельвина
источник
2
Я знаю, что это очень поздний комментарий, но вам действительно не нужно этого делать, если «закрытие файлов и соединений» - единственное, что вы хотите сделать в качестве очистки. Потому что Windows уже закрывает все дескрипторы, связанные с процессом, во время завершения.
Седат Капаноглу,
6
^ Только если эти ресурсы принадлежат завершаемому процессу. Это абсолютно необходимо, если, например, вы автоматизируете скрытое приложение COM (скажем, Word или Excel) в фоновом режиме, и вам нужно обязательно убить его до выхода из приложения и т. Д.
BrainSlugs83,
1
у этого есть короткий ответ stackoverflow.com/questions/2555292/…
barlop

Ответы:

97

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

[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 . Следующая ветка говорит об этом

flq
источник
4
Вы можете использовать это, чтобы отменить выход? Кроме того, когда он выключается!
ingh.am 04
7
Это отлично работает, только bool Handler()должно return false;(он ничего не возвращает в коде), чтобы он работал. Если он возвращает истину, Windows запрашивает диалоговое окно «Завершить процесс сейчас». = D
Cipi 07
3
Похоже , что это решение не работает с Windows 7 для события отключения см social.msdn.microsoft.com/Forums/en/windowscompatibility/thread/...
CharlesB
3
Имейте в виду, что если вы поместите точку останова в метод Handler, он вызовет исключение NullReferenceException. Проверено в VS2010, Windows 7.
Максим
10
У меня это отлично сработало в Windows 7 (64-разрядная версия). Не уверен, почему все говорят, что это не так. Единственные важные изменения, которые я сделал, - это избавиться от операторов enum и switch и «вернуть false» из метода - я делаю всю очистку в теле метода.
BrainSlugs83
25

Полный рабочий пример, работает с ctrl-c, закрывая окна с помощью X и kill:

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
источник
2
Я тестировал это на Windows 7 со всем закомментированным, за Handlerисключением цикла return trueи цикла для подсчета секунд. Приложение продолжает работать при нажатии Ctrl-C, но закрывается через 5 секунд при закрытии с X.
Антониос Хаджигеоргалис
Прошу прощения, но, используя этот код, я могу получить «Очистка завершена», только если я нажму Ctrl + C, а не если закрою кнопкой «X»; в последнем случае я получаю только «Выход из системы из-за внешнего CTRL-C, или завершение процесса, или завершение работы», но затем кажется, что консоль закрывается перед выполнением оставшейся части Handlerметода {с использованием Win10, .NET Framework 4.6.1}
Джакомо Пириноли
в Windows 10 у меня работает CTRL-C, X в окне И завершить процесс в диспетчере задач.
JJ_Coder4Hire
8

Также проверьте:

AppDomain.CurrentDomain.ProcessExit
jmservera
источник
7
Это только кажется, что улавливает выходы из return или Environment.Exit, он не улавливает CTRL + C, CTRL + Break или фактическую кнопку закрытия на консоли.
Kit10
Если вы обрабатываете CTRL + C отдельно, используя Console.CancelKeyPressто ProcessExitсобытие, которое действительно возникает после выполнения всех CancelKeyPressобработчиков событий.
Konard
5

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

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);
    }
}
Жоао Портела
источник
4

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

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

Роб
источник
3
Я согласен с этим ответом. Принудительный выход из приложения и последующая попытка очистки - не выход. Управляйте своим приложением, Noit. Не позволяйте ему контролировать вас.
Randolpho
1
Поток, созданный мной напрямую, не обязательно единственное, что может закрыть мое приложение. Ctrl-C и «кнопка закрытия» - другие способы его завершения. Код, опубликованный Фрэнком, после незначительных изменений подходит идеально.
ZeroKelvin
4

Ответ 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, а вместо этого немедленно завершается.

BCA
источник
3

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

Application.ApplicationExit += CleanupBeforeExit;

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

AppDomain.CurrentDomain.DomainUnload += CleanupBeforeExit;

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

Роб Проус
источник
В справочной документации для DomainUnload говорится: «Делегат EventHandler для этого события может выполнять любые действия завершения до того, как домен приложения будет выгружен». Похоже, это работает в текущем домене. Однако это может не сработать для его нужд, потому что его потоки могут поддерживать домен в рабочем состоянии.
Роб Паркер,
2
Это обрабатывает только CTRL + C и CTRL + Close, он не перехватывает, не возвращаясь, Environment.Exit или нажимая кнопку закрытия.
Kit10
Мне не удается поймать CTRL + C с Mono в Linux.
starbeamrainbowlabs
2

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

    }
}
AJBauer
источник
Интересно, что это, по-видимому, самый надежный ответ. Однако будьте осторожны при изменении размера буфера консоли: если высота буфера меньше, чем высота окна, программа выдаст исключение при запуске.
John Zabroski
1

ссылка упоминалось выше, Charle B в комментарии к 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();
}
Йенс
источник
На самом деле это не работает. У меня есть многооконное приложение WFP, и я использую консоль (AllocConsole как в вашем примере), чтобы показать дополнительную информацию. Проблема в том, что все приложение (все окна) закрывается, если пользователь щелкает (X) в окне консоли. В SetConsoleCtrlHandlerработах, но и останавливает приложение в любом случае , прежде чем какой - либо код в обработчике исполненный (я вижу , контрольные точки и выстрелили прямо тогда приложение останавливается).
Майк Кескинов,
Но я нашел решение, которое работает для меня - я просто ОТКЛЮЧЕН кнопку закрытия. См .: stackoverflow.com/questions/6052992/…
Майк Кескинов,
0

Для интересующихся 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
dko
источник
Вышеупомянутое решение не работает для меня. Vb.net 4.5 framework ControlEventType не решается. Я смог использовать эту идею в качестве решения stackoverflow.com/questions/15317082/…
glant