Как завершить дочерние процессы, когда родительский процесс завершен в C # - PullRequest
16 голосов
/ 13 июля 2010

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

Аналогичный вопрос в С теме Советы по использованию объектов Job. Как использовать его в 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 не работает - порожденный процесс остается живым, когда я убиваю родительский процесс.

Что у меня неверного в этом коде?

Ответы [ 5 ]

9 голосов
/ 12 мая 2011

Я попробовал код выше и действительно, он не работает, жалуясь на плохой размер.Причина этого в том, что используемая структура меняет размер в зависимости от платформы хоста;исходный фрагмент кода (видимый на дюжине веб-сайтов) предполагает 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;
}
7 голосов
/ 15 апреля 2011

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

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

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
4 голосов
/ 13 июля 2010

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

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

3 голосов
/ 22 июля 2010

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

2 голосов
/ 13 июля 2010

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{}                    
        }
    }
}
...