C# 父进程终止时如何终止子进程

发布于 2024-09-09 09:01:06 字数 1356 浏览 6 评论 0原文

任务:如果父进程终止,则自动终止所有子进程。父进程不仅可以以正确的方式终止,还可以通过例如在 ProcessExplorer 中终止来终止。 我该怎么做呢?

С主题中的类似问题建议使用 Job 对象。如何在C#中使用它而不导出外部DLL?


我尝试使用作业对象。但此代码无法正常工作:

  var job = PInvoke.CreateJobObject(null, null);
  var jobli = new PInvoke.JOBOBJECT_BASIC_LIMIT_INFORMATION();

  jobli.LimitFlags = PInvoke.LimitFlags.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
                   | PInvoke.LimitFlags.JOB_OBJECT_LIMIT_PRIORITY_CLASS
                   | PInvoke.LimitFlags.JOB_OBJECT_LIMIT_JOB_TIME
                   | PInvoke.LimitFlags.JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION
                   | PInvoke.LimitFlags.JOB_OBJECT_LIMIT_JOB_MEMORY;

  var res = PInvoke.SetInformationJobObject(job, PInvoke.JOBOBJECTINFOCLASS.JobObjectBasicLimitInformation, jobli, 48);

  if (!res)
  {
    int b = PInvoke.GetLastError();
    Console.WriteLine("Error " + b);
  }

  var Prc = Process.Start(...);

  PInvoke.AssignProcessToJobObject(job, Prc.Handle);

PInvoke.SetInformationJobObject 返回错误。 GetLastError 返回错误 24。 但是,PInvoke.AssignProcessToJobObject 可以工作,并且子进程已添加到作业队列(我可以在 ProcessExplorer 中看到它)。但是,因为 PInvoke.SetInformationJobObject 不起作用 - 当我杀死父进程时,生成的进程仍保持活动状态。

这段代码中有什么不正确的地方?

Task: Auto kill all child processes if parent process terminate. Parent procees can be terminated not only in correct way, but also by killing in ProcessExplorer, for example.
How can I do it?

Similar question in С topic advice to use Job objects. How to use it in C# without exporting external DLL?


I tried to use Job Objects. But this code doesn't work properly:

  var job = PInvoke.CreateJobObject(null, null);
  var jobli = new PInvoke.JOBOBJECT_BASIC_LIMIT_INFORMATION();

  jobli.LimitFlags = PInvoke.LimitFlags.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
                   | PInvoke.LimitFlags.JOB_OBJECT_LIMIT_PRIORITY_CLASS
                   | PInvoke.LimitFlags.JOB_OBJECT_LIMIT_JOB_TIME
                   | PInvoke.LimitFlags.JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION
                   | PInvoke.LimitFlags.JOB_OBJECT_LIMIT_JOB_MEMORY;

  var res = PInvoke.SetInformationJobObject(job, PInvoke.JOBOBJECTINFOCLASS.JobObjectBasicLimitInformation, jobli, 48);

  if (!res)
  {
    int b = PInvoke.GetLastError();
    Console.WriteLine("Error " + b);
  }

  var Prc = Process.Start(...);

  PInvoke.AssignProcessToJobObject(job, Prc.Handle);

PInvoke.SetInformationJobObject returns with error. GetLastError returns error 24.
However, PInvoke.AssignProcessToJobObject works and child process added to Job Queue (I can see it in ProcessExplorer). But, because PInvoke.SetInformationJobObject don't work - spawned process stay alive when I kill parent one.

What do I have incorrect in this code?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

梦忆晨望 2024-09-16 09:01:06

要在 Windows 上终止进程树,仅给出父进程或进程 ID,您需要遍历进程树。

为此,您需要一种方法来获取给定进程的父进程 ID。

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Diagnostics;
using System.Management;

namespace KillProcessTree
{

public static class MyExtensions
{
    public static int GetParentProcessId(this Process p)
    {
        int parentId = 0;
        try
        {
            ManagementObject mo = new ManagementObject("win32_process.handle='" + p.Id + "'");
            mo.Get();
            parentId = Convert.ToInt32(mo["ParentProcessId"]);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.ToString());
            parentId = 0;
        }
        return parentId;
    }
}

一旦掌握了这一点,实际上杀死这棵树并不难。

class Program
{
    /// <summary>
    /// Kill specified process and all child processes
    /// </summary>
    static void Main(string[] args)
    {
        if (args.Length < 1)
        {
            Console.WriteLine("Usage: KillProcessTree <pid>");
            return;
        }

        int pid = int.Parse(args[0]);

        Process root = Process.GetProcessById(pid);
        if (root != null)
        {
            Console.WriteLine("KillProcessTree " + pid);

            var list = new List<Process>();
            GetProcessAndChildren(Process.GetProcesses(), root, list, 1);

            // kill each process
            foreach (Process p in list)
            {
                try
                {
                    p.Kill();
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.ToString());
                }
            }
        }
        else
        {
            Console.WriteLine("Unknown process id: " + root);
        }
    }

    /// <summary>
    /// Get process and children
    /// We use postorder (bottom up) traversal; good as any when you kill a process tree </summary>
    /// </summary>
    /// <param name="plist">Array of all processes</param>
    /// <param name="parent">Parent process</param>
    /// <param name="output">Output list</param>
    /// <param name="indent">Indent level</param>
    private static void GetProcessAndChildren(Process[] plist, Process parent, List<Process> output, int indent)
    {
        foreach (Process p in plist)
        {
            if (p.GetParentProcessId() == parent.Id)
            {
                GetProcessAndChildren(plist, p, output, indent + 1);
            }
        }
        output.Add(parent);
        Console.WriteLine(String.Format("{0," + indent*4 + "} {1}", parent.Id, parent.MainModule.ModuleName));
    }
}
} // namespace

To kill a process tree on windows, given only the parent process or process id, you'll need to walk the process tree.

For that, you'll need a way to get the parent process id for a given process.

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Diagnostics;
using System.Management;

namespace KillProcessTree
{

public static class MyExtensions
{
    public static int GetParentProcessId(this Process p)
    {
        int parentId = 0;
        try
        {
            ManagementObject mo = new ManagementObject("win32_process.handle='" + p.Id + "'");
            mo.Get();
            parentId = Convert.ToInt32(mo["ParentProcessId"]);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.ToString());
            parentId = 0;
        }
        return parentId;
    }
}

Once you have that, actually killing the tree is not hard.

class Program
{
    /// <summary>
    /// Kill specified process and all child processes
    /// </summary>
    static void Main(string[] args)
    {
        if (args.Length < 1)
        {
            Console.WriteLine("Usage: KillProcessTree <pid>");
            return;
        }

        int pid = int.Parse(args[0]);

        Process root = Process.GetProcessById(pid);
        if (root != null)
        {
            Console.WriteLine("KillProcessTree " + pid);

            var list = new List<Process>();
            GetProcessAndChildren(Process.GetProcesses(), root, list, 1);

            // kill each process
            foreach (Process p in list)
            {
                try
                {
                    p.Kill();
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.ToString());
                }
            }
        }
        else
        {
            Console.WriteLine("Unknown process id: " + root);
        }
    }

    /// <summary>
    /// Get process and children
    /// We use postorder (bottom up) traversal; good as any when you kill a process tree </summary>
    /// </summary>
    /// <param name="plist">Array of all processes</param>
    /// <param name="parent">Parent process</param>
    /// <param name="output">Output list</param>
    /// <param name="indent">Indent level</param>
    private static void GetProcessAndChildren(Process[] plist, Process parent, List<Process> output, int indent)
    {
        foreach (Process p in plist)
        {
            if (p.GetParentProcessId() == parent.Id)
            {
                GetProcessAndChildren(plist, p, output, indent + 1);
            }
        }
        output.Add(parent);
        Console.WriteLine(String.Format("{0," + indent*4 + "} {1}", parent.Id, parent.MainModule.ModuleName));
    }
}
} // namespace
凌乱心跳 2024-09-16 09:01:06

我尝试了上面的代码,确实,它不起作用,抱怨尺寸不好。其原因是所使用的结构根据主机平台的不同而改变大小;原始代码片段(在十几个网站上可见)假设是 32 位应用程序。

将结构切换到此(注意 IntPtr 调整大小成员),它将起作用。至少对我来说是这样。

[StructLayout(LayoutKind.Sequential)]
struct JOBOBJECT_BASIC_LIMIT_INFORMATION
{
    public Int64 PerProcessUserTimeLimit;
    public Int64 PerJobUserTimeLimit;
    public Int16 LimitFlags;
    public UIntPtr MinimumWorkingSetSize;
    public UIntPtr MaximumWorkingSetSize;
    public Int16 ActiveProcessLimit;
    public Int64 Affinity;
    public Int16 PriorityClass;
    public Int16 SchedulingClass;
}

I tried the code above and indeed, it does not work, complaining of a bad size. The reason for this is that the structure used changes size depending on the host platform; the original code fragment (seen on a dozen websites) assumes a 32 bit application.

Switch the structure to this (note the IntPtr resizing members) and it will work. At least it did for me.

[StructLayout(LayoutKind.Sequential)]
struct JOBOBJECT_BASIC_LIMIT_INFORMATION
{
    public Int64 PerProcessUserTimeLimit;
    public Int64 PerJobUserTimeLimit;
    public Int16 LimitFlags;
    public UIntPtr MinimumWorkingSetSize;
    public UIntPtr MaximumWorkingSetSize;
    public Int16 ActiveProcessLimit;
    public Int64 Affinity;
    public Int16 PriorityClass;
    public Int16 SchedulingClass;
}
嗼ふ静 2024-09-16 09:01:06

您可以将父进程的 ProcessID 作为参数传递给子进程。然后子进程将负责时不时地检查父进程是否仍在运行。 (通过调用 Process.GetProcessById 。)

跟踪父进程是否存在的另一种方法是使用 Mutex 同步原语。
父应用程序最初将创建一个全局互斥体,其名称为子应用程序所知。孩子们可以不时检查互斥体是否仍然存在,如果不存在则终止。 (一旦父进程关闭,互斥体将被系统自动销毁,无论其关闭方式如何。)

You can pass ProcessID of the parent process as an argument to the child process. And then child processes will be responsible for checking from time to time whether the parent process still running. (By calling Process.GetProcessById.)

Another way to track existence of the parent process is to use Mutex synchronization primitive.
Parent application will initially create a global mutex with the name known by children. Children can check from time to time whether the mutex still exists and terminate if not. (Once the parent process is closed the mutex will be destroyed by the system automatically, regardless of the way it way closed.)

赠我空喜 2024-09-16 09:01:06

你注意过错误代码吗?错误 24 是 ERROR_BAD_LENGTH,这可能意味着 48 不是正确的结构长度。我认为是 44,但您应该执行 sizeof 来确定。

Did you pay attention to the error code? Error 24 is ERROR_BAD_LENGTH, which probably means that 48 isn't the right length of the structure. I think it's 44, but you should do a sizeof to be sure.

缱绻入梦 2024-09-16 09:01:06

当父进程关闭时,Windows 不会强制子进程关闭。当您在任务管理器或进程资源管理器等工具中选择“杀死树”时,该工具实际上会找到所有子进程并将其一一杀死。

如果您想确保在应用程序终止时清除子进程,您可以创建一个实现 IDisposable 的 ProcessManager 类,该类实际创建进程,跟踪它们的实例并在 Dispose 上对每个进程调用 Kill,例如

public class ProcessManager:IDisposable
{
    List<Process> processes=new List<Process>();

    public Process Start(ProcessStartInfo info)
    {
        var newProcess = Process.Start(info);
        newProcess.EnableRaisingEvents = true
        processes.Add(newProcess);
        newProcess.Exited += (sender, e) => processes.Remove(newProcess);
        return newProcess;
    }

    ~ProcessManager()
    {
        Dispose(false);
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        foreach (var process in processes)
        {
            try
            {
                if (!process.HasExited)
                    process.Kill();
            }
            catch{}                    
        }
    }
}

Windows does not force child processes to close when a parent process closes. When you select "Kill Tree" in a tool like Task Manager or Process explorer, the tool actually finds all child processes and kill them one by one.

If you want to ensure that child processes are cleaned when your application terminates, you can create a ProcessManager class that implements IDisposable that actually creates the processes, keeps track of their instances and calls Kill on each one of them on Dispose, e.g.

public class ProcessManager:IDisposable
{
    List<Process> processes=new List<Process>();

    public Process Start(ProcessStartInfo info)
    {
        var newProcess = Process.Start(info);
        newProcess.EnableRaisingEvents = true
        processes.Add(newProcess);
        newProcess.Exited += (sender, e) => processes.Remove(newProcess);
        return newProcess;
    }

    ~ProcessManager()
    {
        Dispose(false);
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        foreach (var process in processes)
        {
            try
            {
                if (!process.HasExited)
                    process.Kill();
            }
            catch{}                    
        }
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文