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

Задача: автоматическое завершение всех дочерних процессов при завершении родительского процесса. Родительские процессы можно прекратить не только корректно, но и убить, например, в 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 не работает - порожденный процесс остается в живых, когда я убиваю родительский.

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


person LionSoft    schedule 13.07.2010    source источник
comment
Ответ на другой вопрос кажется мне хорошим, просто вызовите функции из kernel32. pinvoke.net/default.aspx/kernel32.assignprocesstojobobject   -  person Julien Roncaglia    schedule 13.07.2010


Ответы (5)


Чтобы убить дерево процессов в 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
person milkplus    schedule 15.04.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;
}
person Jim Chaney    schedule 12.05.2011

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

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

person Regent    schedule 13.07.2010
comment
Оба совета бесполезны - дочерние процессы не мои. Это могут быть любые программы. - person LionSoft; 13.07.2010
comment
@LionSoft: Можно ли создать еще один дочерний процесс, который будет отвечать за создание этих дочерних процессов? Затем этот процесс может проверить, работает ли родительский процесс, и убить других дочерних процессов, если нет. - person Regent; 13.07.2010
comment
Но что делать, если этот другой дочерний процесс будет принудительно завершен? - person LionSoft; 13.07.2010
comment
@LionSoft: вы можете воссоздать его снова родительским процессом. Таким образом, если родительский процесс будет убит, то процесс дочернего менеджера убьет других потомков и завершится; но если дочерний менеджер убит, то родительский процесс создаст его снова. - person Regent; 13.07.2010

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

person Gabe    schedule 22.07.2010

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

Если вы хотите убедиться, что дочерние процессы очищаются при завершении вашего приложения, вы можете создать класс 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{}                    
        }
    }
}
person Panagiotis Kanavos    schedule 13.07.2010
comment
К сожалению, когда я с трудом убиваю процесс в ProcessExplorer, у процесса нет возможности выполнить код завершения. Таким образом, ваш exaple будет работать только тогда, когда родительский процесс завершится правильно. Кстати, чтобы исправить работу вашего примера, необходимо добавить строку newProcess.EnableRaisingEvents = true; перед назначением события Exiting. - person LionSoft; 13.07.2010
comment
Как я уже сказал, Windows просто не убивает дочерние процессы, когда родительский процесс умирает. Не существует механизма ОС, обеспечивающего это. Дочерний процесс НЕ принадлежит своему родительскому. Если вы хотите порождать задания обработки, которые гарантированно будут очищены, когда процесс умирает, вы должны использовать потоки. Насчет EnableRaisingEvents вы правы, исправили. - person Panagiotis Kanavos; 14.07.2010
comment
Существует как минимум два механизма ОС для уничтожения порожденных процессов: 1. Присоединение к дочернему процессу в качестве отладчика. 2. Используйте объекты задания с флагом JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE. Но я пока не могу работать обоими этими методами. :( - person LionSoft; 15.07.2010