Подія виходу з програми консолі .NET


80

Чи існує в .NET метод, наприклад подія, для виявлення виходу консольної програми? Мені потрібно очистити деякі потоки та COM-об’єкти.

Я запускаю цикл повідомлень без форми із консольної програми. Здається, що компонент DCOM, який я використовую, вимагає повідомлень про насос програми.

Я спробував додати обробник до Process.GetCurrentProcess.Exited і Process.GetCurrentProcess.Disposed.

Я також спробував додати обробник до подій Application.ApplicationExit та Application.ThreadExit, але вони не запускаються. Можливо, це тому, що я не використовую форму.


2
Process.GetCurrentProcess (). Вихід, що відбувся, повинен запускатися, якщо ви вперше встановили Process.EnableRaisingEvents = true; інакше це не спрацює.
Трійко

2
можливий дублікат виходу з консолі Capture
nawfal

@Triynko як це? Process.Exited запускається після завершення процесу. І якщо власний додаток закрито, тоді код взагалі не виконується. Досить марний imo ..
nawfal

Повний робочий приклад наприкінці цього допису в C #
JJ_Coder4Hire

Відповіді:


79

Ви можете використовувати ProcessExitподію AppDomain:

class Program
{
    static void Main(string[] args)
    {
        AppDomain.CurrentDomain.ProcessExit += new EventHandler(CurrentDomain_ProcessExit);           
        // do some work

    }

    static void CurrentDomain_ProcessExit(object sender, EventArgs e)
    {
        Console.WriteLine("exit");
    }
}

Оновлення

Ось повний приклад програми з порожнім "насосом для повідомлень", що працює в окремому потоці, що дозволяє користувачеві ввести команду quit в консолі, щоб витончено закрити програму. Після циклу в MessagePump ви, мабуть, захочете добре очистити ресурси, що використовуються потоком. Краще це робити там, ніж у ProcessExit з кількох причин:

  • Уникайте проблем із перехресною різьбою; якщо зовнішні COM-об'єкти були створені в потоці MessagePump, то з ними там легше мати справу.
  • На ProcessExit встановлено обмеження за часом (за замовчуванням 3 секунди), тому, якщо очищення займає багато часу, воно може бути невдалим, якщо його внести в обробник подій.

Ось код:

class Program
{
    private static bool _quitRequested = false;
    private static object _syncLock = new object();
    private static AutoResetEvent _waitHandle = new AutoResetEvent(false);

    static void Main(string[] args)
    {
        AppDomain.CurrentDomain.ProcessExit += new EventHandler(CurrentDomain_ProcessExit);
        // start the message pumping thread
        Thread msgThread = new Thread(MessagePump);
        msgThread.Start();
        // read input to detect "quit" command
        string command = string.Empty;
        do
        {
            command = Console.ReadLine();
        } while (!command.Equals("quit", StringComparison.InvariantCultureIgnoreCase));
        // signal that we want to quit
        SetQuitRequested();
        // wait until the message pump says it's done
        _waitHandle.WaitOne();
        // perform any additional cleanup, logging or whatever
    }

    private static void SetQuitRequested()
    {
        lock (_syncLock)
        {
            _quitRequested = true;
        }
    }

    private static void MessagePump()
    {
        do
        {
            // act on messages
        } while (!_quitRequested);
        _waitHandle.Set();
    }

    static void CurrentDomain_ProcessExit(object sender, EventArgs e)
    {
        Console.WriteLine("exit");
    }
}

7
Дякую за пропозицію Фредрік. Здається, ця подія також не запускається.
user79755

Як закінчується ваш процес? Ви називаєте будь-який код, який змушує його вийти?
Фредрік Морк

3
Ах .. це має сенс; ви не виходите з програми, ви вбиваєте її з грубою силою. Немає такої події, яка це підхопить. Вам потрібно застосувати спосіб витонченого виходу з вашої програми.
Fredrik Mörk

4
Ти серйозно? Звичайно, коли будь-яку програму Windows.Forms закриває користувач, закривши основну форму, у вас є шанс виконати очищення! Я впроваджую пакет, і абсолютно неприпустимо просто вмирати від мене, не маючи шансів навіть зафіксувати факт припинення роботи ...
Даг,

2
Це не працює. вставив його в новий проект консолі, вирішив залежності, поставив точку зупинки на CurrentDomain_ProcessExit, і вона не спрацьовує, коли натискається CTRL-C або коли клацається X у вікні. Дивіться мою повну робочу відповідь нижче
JJ_Coder4Hire

47

Ось повне, дуже просте рішення .Net, яке працює у всіх версіях Windows. Просто вставте його в новий проект, запустіть і спробуйте CTRL-C, щоб побачити, як він обробляє:

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

2
Здається, це найкраще рішення C #, яке працює для всіх сценаріїв виходу! Працює ідеально! Дякую :)
HansLindgren

код не працює в Windows XP SP3. якщо ми закриємо програму з диспетчера завдань.
Авінаш

4
Чи буде цей код працювати на Linux у консольному додатку .NET Core? Я не пробував, але "Kernel32" мені здається не портативним ...
ManuelJE

Відмінно. У мене чудово працювало. Зазвичай я перебуваю в Java, але на клієнтському сайті мені довелося написати багатопотокову консольну програму C #, і я не знаю C # настільки добре, що підключається до API подачі даних у реальному часі. Мені потрібно переконатися, що я прибираю речі, встановлюю деякі інші речі на сервері тощо. Це хороше рішення для мене.
TilleyTech

1
Я цим успішно скористався. Однак я використав ManualResetEvent, щоб повідомити, коли починати вимкнення. (І ще один ManualResetEvent зачекав у Handler, щоб подати сигнал, коли завершення роботи завершено.)
Майк Фішер

18

Додаток - це сервер, який просто працює, поки система не вимкнеться або отримає Ctrl + C або вікно консолі не буде закрито.

Через надзвичайний характер заявки неможливо "витончено" вийти. (Можливо, я міг би кодувати іншу програму, яка надсилатиме повідомлення про відключення сервера, але це було б надмірно для однієї програми і все одно недостатньо для певних обставин, наприклад, коли сервер (Фактична ОС) фактично вимикається.)

Через ці обставини я додав " ConsoleCtrlHandler ", де я зупиняю свої потоки та очищаю свої COM-об'єкти тощо ...


Public Declare Auto Function SetConsoleCtrlHandler Lib "kernel32.dll" (ByVal Handler As HandlerRoutine, ByVal Add As Boolean) As Boolean

Public Delegate Function HandlerRoutine(ByVal CtrlType As CtrlTypes) As Boolean

Public Enum CtrlTypes
  CTRL_C_EVENT = 0
  CTRL_BREAK_EVENT
  CTRL_CLOSE_EVENT
  CTRL_LOGOFF_EVENT = 5
  CTRL_SHUTDOWN_EVENT
End Enum

Public Function ControlHandler(ByVal ctrlType As CtrlTypes) As Boolean
.
.clean up code here
.
End Function

Public Sub Main()
.
.
.
SetConsoleCtrlHandler(New HandlerRoutine(AddressOf ControlHandler), True)
.
.
End Sub

Здається, це налаштування працює чудово. Ось посилання на деякий код C # для того самого.


1
Дякую, це мені допомогло :) +1 Ви повинні позначити власну відповідь як відповідь.
леппі

4
Здається, для обробника існує обмеження в 3 секунди. Якщо ви не закінчите вчасно, вас буде немилостиво припинено. Сюди входить, якщо ви сидите на точці зупинки в налагоджувачі!
dan-gph

Це працює, але повний робочий зразок наведено нижче в C #
JJ_Coder4Hire

1
Цей код не запускається, якщо ви використовуєте Завершити процес у диспетчері завдань.
Acaz Souza,

1
@ dan-gph Програма завершується, коли закінчується основний потік. Додайте щось на зразок // утримуйте консоль, щоб вона не закінчилася, поки (! ExitSystem) {Thread.Sleep (500); }
Сьюзен Ван

8

Для випадку CTRL + C ви можете використовувати це:

// Tell the system console to handle CTRL+C by calling our method that
// gracefully shuts down.
Console.CancelKeyPress += new ConsoleCancelEventHandler(Console_CancelKeyPress);


static void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs e)
{
            Console.WriteLine("Shutting down...");
            // Cleanup here
            System.Threading.Thread.Sleep(750);
}

Чи обробляє це вимкнення виходу ctrl + c тощо ...?
user79755

1
Спробував, подія ніколи не спрацьовує. Так само, як і інші події.
user79755

1

Якщо ви використовуєте консольний додаток і перекачуєте повідомлення, чи не можете ви використовувати повідомлення WM_QUIT?


1
Мені теж це цікаво. Я думаю, це може бути так, що Windows не надсилає повідомлення в консольний додаток, але мені це здається дивним. Слід подумати, що вимогою буде наявність насосного повідомлення, а не наявність у нього графічного інтерфейсу користувача ...
The Dag

-1

Як хороший приклад може бути того, щоб перейти до цього проекту і подивитися, як обробляти вихідні процеси граматично або в цьому фрагменті з ВМ, знайденому тут

                ConsoleOutputStream = new ObservableCollection<string>();

                var startInfo = new ProcessStartInfo(FilePath)
                {
                    WorkingDirectory = RootFolderPath,
                    Arguments = StartingArguments,
                    RedirectStandardOutput = true,
                    UseShellExecute = false,
                    CreateNoWindow = true
                };
                ConsoleProcess = new Process {StartInfo = startInfo};

                ConsoleProcess.EnableRaisingEvents = true;

                ConsoleProcess.OutputDataReceived += (sender, args) =>
                {
                    App.Current.Dispatcher.Invoke((System.Action) delegate
                    {
                        ConsoleOutputStream.Insert(0, args.Data);
                        //ConsoleOutputStream.Add(args.Data);
                    });
                };
                ConsoleProcess.Exited += (sender, args) =>
                {
                    InProgress = false;
                };

                ConsoleProcess.Start();
                ConsoleProcess.BeginOutputReadLine();
        }
    }

    private void RegisterProcessWatcher()
    {
        startWatch = new ManagementEventWatcher(
            new WqlEventQuery($"SELECT * FROM Win32_ProcessStartTrace where ProcessName = '{FileName}'"));
        startWatch.EventArrived += new EventArrivedEventHandler(startProcessWatch_EventArrived);

        stopWatch = new ManagementEventWatcher(
            new WqlEventQuery($"SELECT * FROM Win32_ProcessStopTrace where ProcessName = '{FileName}'"));
        stopWatch.EventArrived += new EventArrivedEventHandler(stopProcessWatch_EventArrived);
    }

    private void stopProcessWatch_EventArrived(object sender, EventArrivedEventArgs e)
    {
        InProgress = false;
    }

    private void startProcessWatch_EventArrived(object sender, EventArrivedEventArgs e)
    {
        InProgress = true;
    }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.