2010-07-13 4 views
14

Задача: Автоматическое удаление всех дочерних процессов, если родительский процесс завершается. Родительские методы могут быть прекращены не только правильным способом, но и убийством в ProcessExplorer, например. Как я могу это сделать?Как завершать дочерние процессы при завершении родительского процесса в C#

Подобный вопрос в С topic совет использовать объекты работы. Как использовать его в 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 работает и дочерний процесс добавлен в Job Queue (я могу видеть его в ProcessExplorer). Но, поскольку PInvoke.SetInformationJobObject не работает, процесс, оставшийся в живых, остается в живых, когда я убиваю родителя.

В чем заключается неправильный код?

+0

Другой ответ на вопрос, кажется, хорош для меня, просто вырвите функции из kernel32. http://www.pinvoke.net/default.aspx/kernel32.assignprocesstojobobject –

ответ

4

Вы можете передать ProcessID родительского процесса в качестве аргумента дочернему процессу. И тогда дочерние процессы будут отвечать за время от времени проверять, продолжает ли родительский процесс. (По телефону Process.GetProcessById.)

Другим способом отслеживания существования родительского процесса является использование примитива синхронизации Mutex. Родительское приложение сначала создаст глобальный мьютекс с именем, известным детям. Дети могут время от времени проверять, существует ли мьютекс и прекращается, если нет. (Как только родительский процесс будет закрыт, мьютекс будет автоматически уничтожен системой, независимо от способа его закрытия.)

+1

Обе рекомендации не полезны - дочерние процессы не мои. Это могут быть любые программы. – LionSoft

+2

@LionSoft: У вас есть еще один дочерний процесс, который будет отвечать за создание этих дочерних процессов? Затем этот процесс может проверить, продолжает ли родительский процесс, и убить других детей, если нет. – Regent

+0

Но что делать, если «другой дочерний процесс» будет принудительно прекращен? – LionSoft

2

Windows не заставляет дочерние процессы закрываться при закрытии родительского процесса. Когда вы выбираете «Kill Tree» в таком инструменте, как «Диспетчер задач» или «Проводник процесса», инструмент фактически находит все дочерние процессы и убивает их один за другим.

Если вы хотите, чтобы дочерние процессы были очищены при завершении работы приложения, вы можете создать класс ProcessManager, который реализует IDisposable, который фактически создает процессы, отслеживает их экземпляры и вызывает Kill на каждом из них в 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, процесс не имеет шансов сделать код завершения. Таким образом, ваш exaple будет работать только тогда, когда родительский процесс завершится правильно. BTW, чтобы исправить работу вашего примера, она должна добавить строку ** newProcess.EnableRaisingEvents = true; ** перед назначением * Выход * событие. – LionSoft

+2

Как я уже сказал, Windows просто не убивает дочерние процессы, когда родительский процесс умирает. Для обеспечения этого не существует механизма ОС. Детский процесс НЕ принадлежит его родителям. Если вы хотите создавать задания обработки, которые гарантированно очищаются, когда процесс умирает, вы должны использовать потоки. Вы правы в EnableRaisingEvents, исправили его. –

+2

Существует, по крайней мере, два механизма ОС для уничтожения порожденных процессов: 1. Присоедините к дочернему процессу в качестве отладчика. 2. Использовать объекты заданий с JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE флагом Но пока я не могу работать с этими методами. :( – LionSoft

3

Вы обратите внимание на код ошибки? Ошибка 24 равна ERROR_BAD_LENGTH, что, вероятно, означает, что 48 не является правильной длиной структуры. Я думаю, что это 44, но вы должны сделать sizeof, чтобы быть уверенным.

7

Чтобы убить дерево процессов в окнах, учитывая только родительский процесс или идентификатор процесса, вам нужно пройти дерево процессов.

Для этого вам понадобится способ получить идентификатор родительского процесса для данного процесса.

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