如何在C#中终止父进程终止时的子进程

17

任务: 如果父进程终止,自动终止所有子进程。父进程可以不仅以正确的方式终止,还可以通过ProcessExplorer等工具强制终止。

这个主题中有类似的问题建议使用作业对象(Job Objects)。如何在C#中使用它而不导出外部DLL?


我试过使用作业对象(Job Objects),但是下面的代码无法正常工作:

  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无法正常工作,所以当我终止父进程时,生成的进程仍然存活。

我在这段代码中做错了什么?


其他问题的答案对我来说似乎很好,只需从kernel32中调用函数即可。http://www.pinvoke.net/default.aspx/kernel32.assignprocesstojobobject - Julien Roncaglia
5个回答

9

要在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

9

我尝试了上面的代码,但它确实无法工作,并抱怨有一个错误的大小。原因是使用的结构体根据主机平台而改变大小;原始的代码片段(在十几个网站上看到)假定为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;
}

5
您可以将父进程的ProcessID作为参数传递给子进程。然后,子进程将负责定期检查父进程是否仍在运行(通过调用Process.GetProcessById方法)。
另一种跟踪父进程存在性的方法是使用Mutex同步原语。 父应用程序最初会创建一个全局互斥体,名称为子进程所知。子进程可以定期检查互斥体是否仍存在,如果不存在则终止。(一旦父进程关闭,无论以何种方式关闭,系统都会自动销毁互斥体。)

1
两个建议都不太有用 - 子进程不是我的。它们可以是任何程序。 - LionSoft
2
@LionSoft:你能再创建一个子进程来负责创建那些子进程吗?然后,该进程可以检查父进程是否仍在运行,并在不运行时终止其他子进程。 - Regent
但是如果那个“另一个子进程”被强制终止该怎么办? - LionSoft
1
@LionSoft:您可以通过父进程重新创建它。所以,如果父进程被杀死,那么子管理进程将会杀死其他子进程并退出;但如果子管理进程被杀死,那么父进程将会再次创建它。 - Regent

3

您是否注意到错误代码?错误 24 是 ERROR_BAD_LENGTH,这可能意味着 48 不是结构的正确长度。我认为它应该是 44,但您应该使用 sizeof 来确保。


2

当父进程关闭时,Windows不会强制关闭子进程。当您在诸如任务管理器或进程资源管理器之类的工具中选择“Kill Tree”时,实际上工具会找到所有子进程并逐个关闭它们。

如果您想确保应用程序终止时清除子进程,可以创建一个实现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{}                    
        }
    }
}

不幸的是,当我在ProcessExplorer中强制结束进程时,该进程没有机会执行最终代码。因此,您的示例只有在父进程正确终止时才能正常工作。 顺便说一句,为了使您的示例正常工作,必须在分配“Exiting”事件之前添加以下行**newProcess.EnableRaisingEvents = true;**。 - LionSoft
2
正如我所说,当父进程死亡时,Windows并不会终止子进程。没有操作系统机制来强制执行此项措施,子进程并不隶属于其父进程。 如果您想产生处理作业,并确保在进程死亡时被清理,必须使用线程。 关于EnableRaisingEvents,你是正确的,已经解决了。 - Panagiotis Kanavos
4
至少有两种操作系统机制可以终止派生的进程:
  1. 作为调试器附加到子进程。
  2. 使用JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE标志的作业对象。
但是我目前仍然无法使用这两种方法。:(
- LionSoft
处理并不能保证任何事情。 - pf12345678910
当我关闭主程序时,Dispose方法没有被调用。 - pf12345678910
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接