Как обнаружить закрытие родительского процесса? - PullRequest
3 голосов
/ 21 апреля 2011

Я работаю над консольным приложением .NET, которое должно очищать ресурсы при выходе. Проблема, с которой я сталкиваюсь, заключается в том, что я не получаю никаких уведомлений, если родительский объект cmd закрыт через консольное окно [X], через диспетчер задач / Process Explorer или программно с помощью WM_CLOSE. Я могу жить с неспособностью обработать Kill Process из Task Mgr. или ProcExp. WM_CLOSE родительской консоли - наиболее вероятный способ закрытия этого приложения до завершения его обработки.

Вот события, на которые я пытался зарегистрироваться:

AppDomain.CurrentDomain.ProcessExit += CurrentDomainProcessExit;
AppDomain.CurrentDomain.UnhandledException += CurrentDomainUnhandledException;
Console.CancelKeyPress += ConsoleCancelKeyPress;
Application.ApplicationExit += ApplicationApplicationExit;

Process parentProcess = ProcessInfo.GetParentProcess(
    Process.GetCurrentProcess());
parentProcess.Disposed += ParentDisposed;
parentProcess.Exited += ParentExited;

Process grandParentProcess = ProcessInfo.GetParentProcess(parentProcess);
grandParentProcess.Disposed += GrandParentDisposed;
grandParentProcess.Exited += GrandParentExited;

Эти события происходят правильно, когда я посылаю CTRL + C с консоли или приложение завершает работу без прерывания. Но ни один из них не срабатывает, когда родительское приложение (консоль cmd) закрыто. (Процессы родителя / прародителя не являются CLR, поэтому я не уверен, что когда-нибудь получу эти события Disposed / Exited. Это просто выстрелы в темноте.) Я посмотрел некоторые вещи pInvoke, но я бы не стал отказываться эта дорога, если .NET является вариантом.

Есть ли способ обнаружить и обработать отключение в этих ситуациях? Я открыт для любого решения .NET, pInvoke / Win32 / C / C ++. (Практически любым способом это можно сделать на платформе Windows.)

Спасибо.

P.S. Я все еще работаю с .NET 2.0, поэтому я не могу использовать что-либо, представленное в .NET 3.0 +

Ответы [ 3 ]

2 голосов
/ 21 апреля 2011

Событие Exited должно запускаться всякий раз, когда вы устанавливаете EnableRaisingEvents = true на Process, независимо от того, является ли процесс .Net, собственным или чем-то еще. Если вы на самом деле видите выход Process без запуска события Exited, можете ли вы опубликовать небольшой сценарий воспроизведения?

Кроме того, событие Disposed бесполезно для вашего сценария. Он срабатывает только при вызове Dispose() для экземпляра объекта Process в вашем процессе и не имеет никакого отношения к тому, что происходит внутри процесса ОС, к которому относится объект.

2 голосов
/ 21 апреля 2011

Ваша лучшая ставка, вероятно, будет использовать P / Invoke.Функция Windows API SetConsoleCtrlHandler() может делать то, что вы ожидаете.

Пример кода, украденного из здесь (аналогичный код доступен на MSDN, здесь ).

class Program
{
    [DllImport("Kernel32")]
    public static extern bool SetConsoleCtrlHandler(HandlerRoutine Handler, bool Add);

    // A delegate type to be used as the handler routine 
    // for SetConsoleCtrlHandler.
    public delegate bool HandlerRoutine(CtrlTypes CtrlType);

    // An enumerated type for the control messages
    // sent to the handler routine.
    public enum CtrlTypes 
    {
        CTRL_C_EVENT = 0,
        CTRL_BREAK_EVENT,
        CTRL_CLOSE_EVENT, 
        CTRL_LOGOFF_EVENT = 5,
        CTRL_SHUTDOWN_EVENT
    }

    private static bool ConsoleCtrlCheck(CtrlTypes ctrlType) 
    {
        // Put your own handler here
        return true;
    }

    static void Main(string[] args)
    {
        SetConsoleCtrlHandler(new HandlerRoutine(ConsoleCtrlCheck), true);
    }
}
1 голос
/ 21 апреля 2011

Часть вашей проблемы может заключаться в том, что класс ProcessInfo в вашем примере является частью пространства имен System.Web.Он (цитируя MSDN) «возвращает информацию о рабочих процессах ASP.Net, которые выполняются в рамках модели процессов ASP.Net».Казалось бы, это вряд ли вернет что-либо ужасно полезное для вашего приложения командной строки.

Вам необходимо использовать WMI через пространство имен System.Management.Следующий пример должен выполнить трюк и получить объект System.Diagnostics.Process для непосредственного родителя текущего процесса.В этом примере используется метод Process.WaitForExit(), но подключение обработчика событий также должно работать.

Тем не менее, особенно если вы говорите о консольном приложении, следует отметить, что непосредственным родительским процессом является не обязательно процесс, который фактически породил текущий процесс.Если процесс A порождает консольное приложение B процесса непосредственно с ProcessStartInfo, указывающим UseShellExecute=false, то A будет непосредственным родителем B. Однако, если процесс A порождает процесс B (консольное приложение) с ProcessStartInfo, указывающим UseShellExecute=true,тогда процесс A не будет непосредственным родителем процесса B: между A и B. будет промежуточный процесс (cmd.exe). И если вы используете пакетные файлы * .cmd или код PowerShell...это может быть сложнее.

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

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

using System;
using System.Collections;
using System.Diagnostics;
using System.Management;

namespace WaitOnParentProcessSample
{
    class Program
    {

        static int Main( string[] argv )
        {
            using ( Process parentProcess = GetParentProcess() )
            {
                Console.WriteLine( "Waiting for parent process (pid:{0}) to exit..." , parentProcess.Id );
                parentProcess.WaitForExit();
                Console.WriteLine( "Parent Process Has exited. Condition code cannot be determined" );
            }

            return 0;
        }

        private static Process GetParentProcess()
        {
            Process parentProcess  = null;

            using ( Process currentProcess = Process.GetCurrentProcess() )
            {
                string      filter = string.Format( "ProcessId={0}" , currentProcess.Id );
                SelectQuery query  = new SelectQuery( "Win32_Process" , filter );

                using ( ManagementObjectSearcher   searcher = new ManagementObjectSearcher( query ) )
                using ( ManagementObjectCollection results  = searcher.Get() )
                {

                    if ( results.Count>0 )
                    {
                        if ( results.Count>1 )
                            throw new InvalidOperationException();
                        IEnumerator      resultEnumerator = results.GetEnumerator();
                        bool             fMoved           = resultEnumerator.MoveNext();

                        using ( ManagementObject wmiProcess = (ManagementObject)resultEnumerator.Current )
                        {
                            PropertyData parentProcessId = wmiProcess.Properties["ParentProcessId"];
                            uint         pid = (uint)parentProcessId.Value;

                            parentProcess=Process.GetProcessById( (int)pid );

                        }

                    }

                }

            }

            return parentProcess;

        }

    }

}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...