Kill child process when parent process is killed

前端 未结 15 2409
轻奢々
轻奢々 2020-11-22 05:59

I\'m creating new processes using System.Diagnostics.Process class from my application.

I want this processes to be killed when/if my application has cr

相关标签:
15条回答
  • 2020-11-22 06:44

    Here's an alternative that may work for some when you have control of the code the child process runs. The benefit of this approach is it doesn't require any native Windows calls.

    The basic idea is to redirect the child's standard input to a stream whose other end is connected to the parent, and use that stream to detect when the parent has gone away. When you use System.Diagnostics.Process to start the child, it's easy to ensure its standard input is redirected:

    Process childProcess = new Process();
    childProcess.StartInfo = new ProcessStartInfo("pathToConsoleModeApp.exe");
    childProcess.StartInfo.RedirectStandardInput = true;
    
    childProcess.StartInfo.CreateNoWindow = true; // no sense showing an empty black console window which the user can't input into
    

    And then, on the child process, take advantage of the fact that Reads from the standard input stream will always return with at least 1 byte until the stream is closed, when they will start returning 0 bytes. An outline of the way I ended up doing this is below; my way also uses a message pump to keep the main thread available for things other than watching standard in, but this general approach could be used without message pumps too.

    using System;
    using System.IO;
    using System.Threading;
    using System.Windows.Forms;
    
    static int Main()
    {
        Application.Run(new MyApplicationContext());
        return 0;
    }
    
    public class MyApplicationContext : ApplicationContext
    {
        private SynchronizationContext _mainThreadMessageQueue = null;
        private Stream _stdInput;
    
        public MyApplicationContext()
        {
            _stdInput = Console.OpenStandardInput();
    
            // feel free to use a better way to post to the message loop from here if you know one ;)    
            System.Windows.Forms.Timer handoffToMessageLoopTimer = new System.Windows.Forms.Timer();
            handoffToMessageLoopTimer.Interval = 1;
            handoffToMessageLoopTimer.Tick += new EventHandler((obj, eArgs) => { PostMessageLoopInitialization(handoffToMessageLoopTimer); });
            handoffToMessageLoopTimer.Start();
        }
    
        private void PostMessageLoopInitialization(System.Windows.Forms.Timer t)
        {
            if (_mainThreadMessageQueue == null)
            {
                t.Stop();
                _mainThreadMessageQueue = SynchronizationContext.Current;
            }
    
            // constantly monitor standard input on a background thread that will
            // signal the main thread when stuff happens.
            BeginMonitoringStdIn(null);
    
            // start up your application's real work here
        }
    
        private void BeginMonitoringStdIn(object state)
        {
            if (SynchronizationContext.Current == _mainThreadMessageQueue)
            {
                // we're already running on the main thread - proceed.
                var buffer = new byte[128];
    
                _stdInput.BeginRead(buffer, 0, buffer.Length, (asyncResult) =>
                    {
                        int amtRead = _stdInput.EndRead(asyncResult);
    
                        if (amtRead == 0)
                        {
                            _mainThreadMessageQueue.Post(new SendOrPostCallback(ApplicationTeardown), null);
                        }
                        else
                        {
                            BeginMonitoringStdIn(null);
                        }
                    }, null);
            }
            else
            {
                // not invoked from the main thread - dispatch another call to this method on the main thread and return
                _mainThreadMessageQueue.Post(new SendOrPostCallback(BeginMonitoringStdIn), null);
            }
        }
    
        private void ApplicationTeardown(object state)
        {
            // tear down your application gracefully here
            _stdInput.Close();
    
            this.ExitThread();
        }
    }
    

    Caveats to this approach:

    1. the actual child .exe that is launched must be a console application so it remains attached to stdin/out/err. As in the above example, I easily adapted my existing application that used a message pump (but didn't show a GUI) by just creating a tiny console project that referenced the existing project, instantiating my application context and calling Application.Run() inside the Main method of the console .exe.

    2. Technically, this merely signals the child process when the parent exits, so it will work whether the parent process exited normally or crashed, but its still up to the child processes to perform its own shutdown. This may or may not be what you want...

    0 讨论(0)
  • 2020-11-22 06:45

    I was looking for a solution to this problem that did not require unmanaged code. I was also not able to use standard input/output redirection because it was a Windows Forms application.

    My solution was to create a named pipe in the parent process and then connect the child process to the same pipe. If the parent process exits then the pipe becomes broken and the child can detect this.

    Below is an example using two console applications:

    Parent

    private const string PipeName = "471450d6-70db-49dc-94af-09d3f3eba529";
    
    public static void Main(string[] args)
    {
        Console.WriteLine("Main program running");
    
        using (NamedPipeServerStream pipe = new NamedPipeServerStream(PipeName, PipeDirection.Out))
        {
            Process.Start("child.exe");
    
            Console.WriteLine("Press any key to exit");
            Console.ReadKey();
        }
    }
    

    Child

    private const string PipeName = "471450d6-70db-49dc-94af-09d3f3eba529"; // same as parent
    
    public static void Main(string[] args)
    {
        Console.WriteLine("Child process running");
    
        using (NamedPipeClientStream pipe = new NamedPipeClientStream(".", PipeName, PipeDirection.In))
        {
            pipe.Connect();
            pipe.BeginRead(new byte[1], 0, 1, PipeBrokenCallback, pipe);
    
            Console.WriteLine("Press any key to exit");
            Console.ReadKey();
        }
    }
    
    private static void PipeBrokenCallback(IAsyncResult ar)
    {
        // the pipe was closed (parent process died), so exit the child process too
    
        try
        {
            NamedPipeClientStream pipe = (NamedPipeClientStream)ar.AsyncState;
            pipe.EndRead(ar);
        }
        catch (IOException) { }
    
        Environment.Exit(1);
    }
    
    0 讨论(0)
  • 2020-11-22 06:45

    Just my 2018 version. Use it aside your Main() method.

        using System.Management;
        using System.Diagnostics;
    
        ...
    
        // Called when the Main Window is closed
        protected override void OnClosed(EventArgs EventArgs)
        {
            string query = "Select * From Win32_Process Where ParentProcessId = " + Process.GetCurrentProcess().Id;
            ManagementObjectSearcher searcher = new ManagementObjectSearcher(query);
            ManagementObjectCollection processList = searcher.Get();
            foreach (var obj in processList)
            {
                object data = obj.Properties["processid"].Value;
                if (data != null)
                {
                    // retrieve the process
                    var childId = Convert.ToInt32(data);
                    var childProcess = Process.GetProcessById(childId);
    
                    // ensure the current process is still live
                    if (childProcess != null) childProcess.Kill();
                }
            }
            Environment.Exit(0);
        }
    
    0 讨论(0)
  • 2020-11-22 06:47

    I had the same problem. I was creating child processes that never got killed if my main app crashed. I had to destroy manually child processes when debugging. I found that there was no need to make the children somewhat depend on parent. In my main, I added a try catch to do a CleanUp() of child processes on exit.

        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            try
            {
                Application.Run(new frmMonitorSensors());
            }
            catch(Exception ex)
            {
                CleanUp();
                ErrorLogging.Add(ex.ToString());
            }
        }
    
        static private void CleanUp()
        {
            List<string> processesToKill = new List<string>() { "Process1", "Process2" };
            foreach (string toKill in processesToKill)
            {
                Process[] processes = Process.GetProcessesByName(toKill);
                foreach (Process p in processes)
                {
                    p.Kill();
                }
            }
        }
    
    0 讨论(0)
  • 2020-11-22 06:50

    One way is to pass PID of parent process to the child. The child will periodically poll if the process with the specified pid exists or not. If not it will just quit.

    You can also use Process.WaitForExit method in child method to be notified when the parent process ends but it might not work in case of Task Manager.

    0 讨论(0)
  • 2020-11-22 06:53

    This post is intended as an extension to @Matt Howells' answer, specifically for those who run into problems with using Job Objects under Vista or Win7, especially if you get an access denied error ('5') when calling AssignProcessToJobObject.

    tl;dr

    To ensure compatibility with Vista and Win7, add the following manifest to the .NET parent process:

    <?xml version="1.0" encoding="utf-8" standalone="yes"?>
    <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
      <v3:trustInfo xmlns:v3="urn:schemas-microsoft-com:asm.v3">
        <v3:security>
          <v3:requestedPrivileges>
            <v3:requestedExecutionLevel level="asInvoker" uiAccess="false" />
          </v3:requestedPrivileges>
        </v3:security>
      </v3:trustInfo>
      <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
        <!-- We specify these, in addition to the UAC above, so we avoid Program Compatibility Assistant in Vista and Win7 -->
        <!-- We try to avoid PCA so we can use Windows Job Objects -->
        <!-- See https://stackoverflow.com/questions/3342941/kill-child-process-when-parent-process-is-killed -->
    
        <application>
          <!--The ID below indicates application support for Windows Vista -->
          <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
          <!--The ID below indicates application support for Windows 7 -->
          <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
        </application>
      </compatibility>
    </assembly>
    

    Note that when you add new manifest in Visual Studio 2012 it will contain the above snippet already so you do not need to copy it from hear. It will also include a node for Windows 8.

    full explanation

    Your job association will fail with an access denied error if the process you're starting is already associated with another job. Enter Program Compatibility Assistant, which, starting in Windows Vista, will assign all kinds of processes to its own jobs.

    In Vista you can mark your application to be excluded from PCA by simply including an application manifest. Visual Studio seems to do this for .NET apps automatically, so you're fine there.

    A simple manifest no longer cuts it in Win7. [1] There, you have to specifically specify that you're compatible with Win7 with the tag in your manifest. [2]

    This led me to worry about Windows 8. Will I have to change my manifest once again? Apparently there's a break in the clouds, as Windows 8 now allows a process to belong to multiple jobs. [3] So I haven't tested it yet, but I imagine that this madness will be over now if you simply include a manifest with the supportedOS information.

    Tip 1: If you're developing a .NET app with Visual Studio, as I was, here [4] are some nice instructions on how to customize your application manifest.

    Tip 2: Be careful with launching your application from Visual Studio. I found that, after adding the appropriate manifest, I still had problems with PCA when launching from Visual Studio, even if I used Start without Debugging. Launching my application from Explorer worked, however. After manually adding devenv for exclusion from PCA using the registry, starting applications that used Job Objects from VS started working as well. [5]

    Tip 3: If you ever want to know if PCA is your problem, try launching your application from the command line, or copy the program to a network drive and run it from there. PCA is automatically disabled in those contexts.

    [1] http://blogs.msdn.com/b/cjacks/archive/2009/06/18/pca-changes-for-windows-7-how-to-tell-us-you-are-not-an-installer-take-2-because-we-changed-the-rules-on-you.aspx

    [2] http://ayende.com/blog/4360/how-to-opt-out-of-program-compatibility-assistant

    [3] http://msdn.microsoft.com/en-us/library/windows/desktop/ms681949(v=vs.85).aspx: "A process can be associated with more than one job in Windows 8"

    [4] How can I embed an application manifest into an application using VS2008?

    [5] How to stop the Visual Studio debugger starting my process in a job object?

    0 讨论(0)
提交回复
热议问题