Захват консоли выход C#
У меня есть консольное приложение, которое содержит довольно много ниток. Существуют потоки, которые отслеживают определенные условия и завершают программу, если они верны. Это прекращение может произойти в любое время.
Мне нужно событие, которое может быть вызвано при закрытии программы, чтобы я мог очистить все другие потоки и закрыть все дескрипторы файлов и соединения правильно. Я не уверен, что он уже встроен в .NET framework, поэтому я спрашиваю, прежде чем писать моя собственность.
мне было интересно, было ли событие по строкам:
MyConsoleProgram.OnExit += CleanupBeforeExit;
10 ответов:
Я не уверен, где я нашел код в интернете, но я нашла его в одном из моих старых проектов. Это позволит вам сделать код очистки в консоли, например, когда он резко закрыт или из-за выключения...
[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. Следующее thread переговоры о это
полный рабочий пример, работает с 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.."); } } }
есть для WinForms приложений;
Application.ApplicationExit += CleanupBeforeExit;
для консольных приложений, попробовать
AppDomain.CurrentDomain.DomainUnload += CleanupBeforeExit;
но я не уверен, в какой момент это вызывается или если он будет работать из текущего домена. Подозреваю, что нет.
похоже, что у вас есть потоки, непосредственно завершающие приложение? Возможно, было бы лучше иметь сигнал потока основной поток, чтобы сказать, что приложение должно быть прекращено.
на получать этот сигнал, главный поток может чисто выключить другие потоки и окончательно закрыть вниз.
У меня была аналогичная проблема, просто мое консольное приложение будет работать в бесконечном цикле с одним упреждающим утверждением на середине. Вот мое решение:
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); } }
на ссылке упомянутый выше Шарль Б в комментарии к flq
в глубине души говорит:
SetConsoleCtrlHandler не будет работать на windows7, если вы ссылаетесь на user32
где-то еще в потоке предлагается создать скрытое окно. Поэтому я создаю winform и в onload я прикреплен к консоли и выполняю original 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(); }
для тех, кто заинтересован в 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
Visual Studio 2015 + Windows 10
- разрешить очистку
- один экземпляр приложения
- некоторые goldplating
код:
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(); } } }
ответ 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, но вместо этого немедленно завершается.