Вихід із консолі захоплення C #


92

У мене є консольний додаток, який містить досить багато потоків. Є потоки, які контролюють певні умови та припиняють програму, якщо вони відповідають дійсності. Це припинення може відбутися в будь-який час.

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

Мені було цікаво, чи не відбулася подія на зразок:

MyConsoleProgram.OnExit += CleanupBeforeExit;

2
Я знаю, що це дуже пізній коментар, але вам насправді не потрібно цього робити, якщо "закриття файлів та з'єднань" - це єдине, що ви хочете зробити як очищення. Оскільки під час завершення Windows вже закриває всі дескриптори, пов’язані з процесом.
Седат Капаноглу

6
^ Лише якщо ці ресурси належать процесу, що припиняється. Це вкрай необхідно, якщо, наприклад, ви автоматизуєте приховану програму COM (скажімо, Word або Excel) у фоновому режимі, і вам потрібно переконатись убити її до виходу програми тощо
BrainSlugs83

1
це має короткий шукає відповідь stackoverflow.com/questions/2555292 / ...
barlop

Відповіді:


96

Я не впевнений, де я знайшов код в Інтернеті, але зараз я знайшов його в одному зі своїх старих проектів. Це дозволить вам виконати очищення коду у вашій консолі, наприклад, коли вона різко закрита або через вимкнення ...

[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 . Про це говорить наступний потік


4
Чи можете ви використати це, щоб скасувати вихід? За винятком випадків, коли він вимикається!
ingh.am

7
Це чудово працює, тільки bool Handler()повинен return false;(він не повертає нічого в коді), щоб він працював. Якщо воно повертає істину, Windows запитує діалогове вікно "Завершити процес зараз". = D
Cipi

3
Схоже, це рішення не працює з Windows 7 для події вимкнення, див. Social.msdn.microsoft.com/Forums/en/windowscompatibility/thread/…
CharlesB

3
Майте на увазі, що якщо ви встановите точку зупинки в методі 'Handler', вона викличе NullReferenceException. Перевірено у VS2010, Windows 7.
Максим

10
Для мене це чудово працювало в Windows 7 (64-розрядна). Не впевнений, чому всі говорять, що ні. Єдиними основними змінами, які я зробив, було позбавлення від оператора перерахування та перемикання та "повернення помилкового" з методу - я виконую всі свої очищення в тілі методу.
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..");
        }
    }
}

2
Я перевірив це на Windows 7 з усім коментарем, за Handlerвинятком return trueциклу and while для підрахунку секунд. Програма продовжує працювати на ctrl-c, але закривається через 5 секунд при закритті X.
Антоніос Хаджигеоргаліс,

Вибачте, але використовуючи цей код, я можу отримати "Очищення завершено", лише якщо натиснути Ctrl + C, а не якщо закрити кнопкою "X"; в останньому випадку я отримую лише "Вихід із системи через зовнішній CTRL-C, або вбивство процесу, або вимкнення", але тоді здається, що консоль закривається перед виконанням решти частини Handlerметоду {з використанням Win10, .NET Framework 4.6.1}
Джакомо Пірінолі

8

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

AppDomain.CurrentDomain.ProcessExit

7
Здається, це лише ловить виходи з повернення або середовища. Вихід, він не ловить 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
Я повинен погодитися з цією відповіддю. Примусовий вихід програми, а потім спроба прибрати після цього - це не той шлях. Контролюй свою програму, Нойт. Не дозволяйте йому керувати вами.
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, а замість цього негайно припиняється.


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

    }
}

Цікаво, що це видається найбільш надійною відповіддю. Однак будьте обережні щодо зміни розміру буфера консолі: якщо висота буфера менше висоти вікна, програма видасть виняток під час запуску.
John Zabroski

1

Посилання згадувалося вище, Charle B в коментарі до FLQ

У глибині душі каже:

SetConsoleCtrlHandler не працюватиме в Windows7, якщо ви зробите посилання на user32

Десь у потоці пропонується створити приховане вікно. Тому я створюю winform і під час завантаження я приєднуюсь до консолі та виконую оригінальний 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як у вашому прикладі), щоб показати деяку додаткову інформацію. Проблема полягає в тому, що вся програма (у всіх Windows) закривається, якщо користувач натискає на (X) у вікні консолі. У SetConsoleCtrlHandlerроботах, а й зупиняє додаток в будь-якому випадку , перш ніж будь - або код в обробнику виконаний (я бачу , контрольні точки і вистрілили прямо тоді додаток зупиняється).
Майк Кескінов

Але я знайшов рішення, яке мені підходить - я просту кнопку ВІМКНЕНО закрити. Див: stackoverflow.com/questions/6052992 / ...
Mike Кескінов

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

Вищевказане рішення для мене не працює vb.net 4.5 framework ControlEventType не вирішується. Я був в змозі використати цю ідею в якості рішення stackoverflow.com/questions/15317082 / ...
glant
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.