2010-07-13 27 views
14

Tarea: Elimine automáticamente todos los procesos secundarios si el proceso principal finaliza. Los procedimientos de los padres pueden darse por terminados no solo de manera correcta, sino también matando en ProcessExplorer, por ejemplo. ¿Cómo puedo hacerlo?Cómo termina el proceso secundario cuando finaliza el proceso primario en C#

Pregunta similar en С topic consejos para usar Objetos de trabajo. ¿Cómo usarlo en C# sin exportar DLL externo?


He intentado utilizar Job Objects. Sin embargo, este código no funciona correctamente:

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 vuelve con el error. GetLastError devuelve el error 24. Sin embargo, PInvoke.AssignProcessToJobObject funciona y el proceso secundario se agrega a la cola de trabajos (puedo verlo en ProcessExplorer). Pero, debido a que PInvoke.SetInformationJobObject no funciona, el proceso generado se mantiene activo cuando elimino uno principal.

¿Qué es incorrecto en este código?

+0

La otra respuesta a la pregunta parece ser buena para mí, solo pinvoke las funciones de kernel32. http://www.pinvoke.net/default.aspx/kernel32.assignprocesstojobobject –

Respuesta

4

Puede pasar ProcessID del proceso principal como argumento del proceso secundario. Y a continuación, los procesos secundarios serán responsables de verificar periódicamente si el proceso principal aún se está ejecutando. (Llamando al Process.GetProcessById.)

Otra forma de rastrear la existencia del proceso principal es usar la primitiva de sincronización Mutex. La aplicación principal creará inicialmente un mutex global con el nombre conocido por los niños. Los niños pueden verificar de vez en cuando si el mutex todavía existe y terminar si no. (Una vez que se cierra el proceso primario, el sistema destruirá el mutex automáticamente, independientemente de cómo se haya cerrado).

+1

Ambos consejos no son útiles. Los procesos hijo no son míos. Pueden ser cualquier programa – LionSoft

+2

@LionSoft: ¿Puede tener otro proceso secundario que será responsable de crear esos procesos secundarios? Entonces, ese proceso puede verificar si el proceso principal aún se está ejecutando y matar a otros niños si no es así. – Regent

+0

¿Pero qué hacer si ese "otro proceso secundario" será forzado a terminar? – LionSoft

2

Windows no obliga a los procesos secundarios a cerrarse cuando se cierra un proceso principal. Cuando selecciona "Kill Tree" en una herramienta como Task Manager o Process Explorer, la herramienta realmente encuentra todos los procesos secundarios y los mata uno por uno.

Si desea asegurarse de que los procesos secundarios se limpien cuando finalice su aplicación, puede crear una clase ProcessManager que implementa IDisposable que realmente crea los procesos, realiza un seguimiento de sus instancias y llama a Kill en cada uno de ellos en Dispose, p.ej

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

Desafortunadamente, cuando apenas mato el proceso en ProcessExplorer el proceso no tiene posibilidad de hacer el código de finalización. Por lo tanto, su exaple funcionará solo cuando el proceso principal finalice correctamente. BTW, para corregir el trabajo de su ejemplo, tiene que agregar la línea ** newProcess.EnableRaisingEvents = true; ** antes de asignar el evento * Exiting *. – LionSoft

+2

Como dije, Windows simplemente no mata procesos secundarios cuando un proceso principal muere. No hay un mecanismo de sistema operativo para hacer cumplir eso. Un proceso hijo NO pertenece a su padre. Si desea generar trabajos de procesamiento que se garantiza que se limpiarán cuando muere un proceso, debe usar subprocesos. Tiene razón sobre EnableRaisingEvents, lo arregló. –

+2

Existen al menos dos mecanismos del sistema operativo para matar procesos engendrados: 1. Adjunte al proceso hijo como depurador. 2. Usar objetos de trabajo con el indicador JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE Pero no puedo trabajar ambos métodos hasta ahora. :( – LionSoft

3

¿Prestaste atención al código de error? El error 24 es ERROR_BAD_LENGTH, lo que probablemente significa que 48 no es la longitud correcta de la estructura. Creo que es 44, pero deberías hacer un sizeof para estar seguro.

7

Para matar un árbol de procesos en Windows, dado solo el proceso principal o la identificación del proceso, deberá recorrer el árbol de procesos.

Para eso, necesitará una forma de obtener la identificación del proceso principal para un proceso determinado.

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

Una vez que tienes eso, matar al árbol no es difícil.

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

He intentado el código anterior y, de hecho, no funciona, quejándose de un mal tamaño.La razón para esto es que la estructura utilizada cambia de tamaño dependiendo de la plataforma del host; el fragmento de código original (visto en una docena de sitios web) asume una aplicación de 32 bits.

Cambie la estructura a esto (tenga en cuenta los miembros de redimensionamiento de IntPtr) y funcionará. Al menos lo hizo por mí.

[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; 
} 
Cuestiones relacionadas