Как завершать дочерние процессы, когда родительский процесс завершается в С#

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

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

Что мне нужно в этом коде?

Ответ 1

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

Ответ 2

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

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

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

Ответ 3

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

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

Ответ 4

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

Ответ 5

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