2010-07-13 233 views
14

任务:如果父进程终止,自动终止所有子进程。例如,父进程不仅可以以正确的方式终止,还可以在ProcessExplorer中终止。 我该怎么办?父进程在C#中终止时如何终止子进程

类似的问题在С topic建议使用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不起作用 - 当我杀掉父亲时,衍生的过程保持活跃状态​​。

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

+0

另一个问题的答案似乎对我很好,只是从kernel32中调用函数。 http://www.pinvoke.net/default.aspx/kernel32.assignprocesstojobobject – 2010-07-13 08:14:46

回答

4

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

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

+1

这两个建议不使用完整的子进程不是我的。他们可以是任何程序。 – LionSoft 2010-07-13 09:32:41

+2

@LionSoft:您是否可以有另一个负责创建这些子进程的子进程?然后,该进程可以检查父进程是否仍在运行,如果没有,则会终止其他子进程。 – Regent 2010-07-13 10:31:58

+0

但是如果这个“另一个子进程”将被强制终止怎么办? – LionSoft 2010-07-13 11:22:42

2

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

如果您希望确保在应用程序终止时清理子进程,您可以创建一个ProcessManager类,该类实现IDisposable,实际创建进程,跟踪其实例并在Dispose上调用其每个实例,例如

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{}      
     } 
    } 
} 
+0

不幸的是,当我在ProcessExplorer中几乎没有杀死进程时,进程没有机会完成最终代码。所以,只有当父进程正确终止时,你的例程才能工作。 顺便说一句,要纠正你的例子,它必须添加行 ** newProcess.EnableRaisingEvents = true; **分配*退出*事件之前。 – LionSoft 2010-07-13 09:43:02

+2

正如我所说的,当父进程死亡时,Windows并不会终止子进程。没有操作系统机制来执行该操作。子过程不属于其父项。 如果您希望产生在进程死亡时保证清理的处理作业,则必须使用线程。 你是对的EnableRaisingEvents,修正它。 – 2010-07-14 09:08:31

+2

至少有两种操作系统机制可以杀死衍生进程: 1.作为调试器附加到子进程。 2.使用JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE标志的作业对象标志 但我目前无法使用这两种方法。 :( – LionSoft 2010-07-15 05:39:15

3

您是否注意到错误代码?错误24是ERROR_BAD_LENGTH,这可能意味着48不是结构的正确长度。我认为它是44,但你应该做一个sizeof是肯定的。

7

要杀死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 
8

我试过上面的代码,实际上,它不起作用,抱怨尺寸不合适。这是因为所使用的结构根据主机平台而改变了大小;原始代码片段(可在十几个网站上看到)假设一个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; 
}