【问题标题】:How to detect closing of parent process?如何检测父进程的关闭?
【发布时间】:2011-04-20 20:23:58
【问题描述】:

我正在开发一个 .NET 控制台应用程序,该应用程序需要在退出时清理资源。我遇到的问题是,如果通过控制台窗口 [X]、任务管理器/进程资源管理器或使用 WM_CLOSE 以编程方式关闭 cmd 父级,我不会收到任何通知。我可以忍受无法处理来自 Task Mgr 的 Kill Process。或 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 平台上完成。)

谢谢。

附:我仍在使用 .NET 2.0,因此无法使用 .NET 3.0+ 中引入的任何内容

【问题讨论】:

  • 需要清理哪些资源,需要进行哪些清理工作?
  • 您说父级是控制台应用程序,但后来您提到了 WM_CLOSE。父级是控制台应用程序(在这种情况下它没有窗口)还是窗口应用程序(在这种情况下您可以向它发送 WM_CLOSE)?
  • @John:该进程执行其他作业,如果它过早停止,我需要停止这些作业。否则,子作业将被孤立并在没有生成过程的情况下运行完成。
  • @Gabe:控制台窗口本身可以接受 WM_CLOSE 并关闭。我从那个控制台开始的进程也被关闭了。
  • @Paul:我认为如果子进程完成父进程在父进程不再在那里观看时启动的进程是一件坏事?我的建议是 - 让父级永远不会停止 - 将其设为 Windows 服务,它具有启动和停止的正式概念。

标签: c# .net vb.net winapi


【解决方案1】:

您最好的选择可能是使用 P/Invoke。 Windows API 函数SetConsoleCtrlHandler() 可能会如您所愿。

下面的示例代码是从here 窃取的(类似代码可在 MSDN 上找到,here)。

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

【讨论】:

  • 这是一个很好的答案,不幸的是,当我查看父应用程序的代码时,此方法已在使用中,并且事件被捕获而无法到达我的应用程序。我将不得不采用 C 计划并实施 IPC 机制来处理这个问题。
【解决方案2】:

只要您在Process 上设置EnableRaisingEvents = true,就应该触发Exited 事件,无论该进程是.Net、本机还是其他任何进程。如果您实际上看到 Process 退出而没有触发 Exited 事件,您能否发布一个小的重现场景?

此外,Disposed 事件对您的方案毫无用处。它仅在您在进程中调用 Process 对象实例上的 Dispose() 时触发,并且与对象引用的 OS 进程中发生的任何事情无关。

【讨论】:

    【解决方案3】:

    您的部分问题可能是您示例中的ProcessInfo 类是System.Web 命名空间的一部分。它(引用 MSDN)“返回有关在 ASP.Net 进程模型下运行的 ASP.Net 工作进程的信息。”这似乎不太可能返回对您的命令行应用非常有用的任何内容。

    您需要通过 System.Management 命名空间使用 WMI。以下示例应该可以解决问题,并为您获取当前进程的直接父进程的 System.Diagnostics.Process 对象。此示例使用Process.WaitForExit() 方法,但连接事件处理程序也应该可以工作。

    但是,您应该注意,尤其是在您谈论控制台应用程序时,直接父进程不一定不一定是实际产生当前进程的进程。如果进程 A 使用 ProcessStartInfo 指定 UseShellExecute=false 直接生成进程控制台应用程序 B,则 A 将是 B 的直接父进程。但是,如果进程 A 使用 ProcessStartInfo 指定 UseShellExecute=true 生成进程 B(控制台应用程序),那么进程 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;
    
            }
    
        }
    
    }
    

    【讨论】:

    • 有趣的想法,但这些进程要么是服务,要么是调用另一个的控制台应用程序。完全不涉及 IIS。
    • 您在Process parentProcess = ProcessInfo.GetParentProcess( Process.GetCurrentProcess()); 中引用的实际ProcessInfo 类是什么?除非它是您自己的一些自定义代码,否则 .Net 框架中该名称的唯一类是 System.Web.ProcessInfo 并且特定于 Asp.Net 工作进程。但无论哪种方式,在我的示例代码中查询 WMI 都会让您获得父进程。
    • 没错。这是一个内部实用程序类。由于我们没有使用 System.Web 命名空间,因此命名冲突从未成为问题。不过,我认为我会将其重命名为 ProcessUtil 或类似名称以避免将来出现任何混淆。
    猜你喜欢
    • 2013-08-27
    • 1970-01-01
    • 2011-07-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多