2010-07-13 34 views
14

Tâche: Tuer automatiquement tous les processus enfants si le processus parent se termine. Les procédures parent peuvent être terminées non seulement de manière correcte, mais aussi en supprimant dans ProcessExplorer, par exemple. Comment puis-je le faire?Comment finit le processus fils lorsque le processus parent se termine en C#

Question similaire dans С topic conseils pour utiliser des objets de travail. Comment l'utiliser en C# sans exporter de DLL externe?


J'ai essayé d'utiliser des objets de travail. Mais ce code ne fonctionne pas correctement:

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 retourne avec l'erreur. GetLastError renvoie l'erreur 24. Cependant, PInvoke.AssignProcessToJobObject fonctionne et le processus enfant est ajouté à la file d'attente des tâches (je peux le voir dans ProcessExplorer). Mais, parce que PInvoke.SetInformationJobObject ne fonctionne pas - processus engendré rester en vie quand je tue un parent.

Qu'est-ce que j'ai de incorrect dans ce code?

+0

Les autres questions me semblent bonnes, il suffit de faire un pinping des fonctions de kernel32. http://www.pinvoke.net/default.aspx/kernel32.assignprocesstojobobject –

Répondre

4

Vous pouvez passer le ProcessID du processus parent en tant qu'argument au processus enfant. Ensuite, les processus fils seront chargés de vérifier de temps en temps si le processus parent est toujours en cours d'exécution. (En appelant le Process.GetProcessById.)

Une autre façon de suivre l'existence du processus parent consiste à utiliser la primitive de synchronisation Mutex. L'application parente crée initialement un mutex global dont le nom est connu par les enfants. Les enfants peuvent vérifier de temps en temps si le mutex existe encore et se terminer si ce n'est pas le cas. (Une fois le processus parent fermé, le mutex sera automatiquement détruit par le système, quel que soit le mode de fermeture.)

+1

Les deux conseils ne sont pas utiles - les processus fils ne m'appartiennent pas. Ils peuvent être des programmes. – LionSoft

+2

@LionSoft: Pouvez-vous avoir un autre processus enfant qui sera responsable de la création de ces processus enfants? Ensuite, ce processus peut vérifier si le processus parent est toujours en cours d'exécution et tuer d'autres enfants sinon. – Regent

+0

Mais que faire si ce "processus d'un autre enfant" est forcé de se terminer? – LionSoft

2

Windows ne force pas les processus enfants à se fermer lorsqu'un processus parent se ferme. Lorsque vous sélectionnez "Tuer l'arbre" dans un outil tel que Gestionnaire des tâches ou Explorateur de processus, l'outil recherche tous les processus enfants et les supprime un par un. Si vous voulez vous assurer que les processus enfants sont nettoyés lorsque votre application se termine, vous pouvez créer une classe ProcessManager qui implémente IDisposable qui crée réellement les processus, garde la trace de leurs instances et appelle Kill sur chacun d'eux sur Dispose, par exemple

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

Malheureusement, quand je tue à peine le processus dans ProcessExplorer, le processus n'a aucune chance de faire du code de finalisation. Ainsi, votre exaple ne fonctionnera que lorsque le processus parent se terminera correctement. BTW, pour corriger votre exemple de travail, il faut ajouter la ligne ** newProcess.EnableRaisingEvents = true; ** avant d'assigner * Exiting * événement. – LionSoft

+2

Comme je l'ai dit, Windows ne tue pas les processus enfants lorsqu'un processus parent meurt. Il n'y a pas de mécanisme OS pour l'appliquer. Un processus enfant n'appartient PAS à son parent. Si vous souhaitez générer des tâches de traitement dont le nettoyage est garanti lors du processus, vous devez utiliser des threads. Vous avez raison à propos de EnableRaisingEvents, vous l'avez corrigé. –

+2

Il existe au moins deux mécanismes du système d'exploitation pour supprimer les processus générés: 1. Attachez au processus enfant en tant que débogueur. 2. Utilisez les objets de tâche avec l'indicateur JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE Mais je ne peux pas utiliser ces deux méthodes jusqu'à présent. :( – LionSoft

3

Avez-vous fait attention au code d'erreur? L'erreur 24 est ERROR_BAD_LENGTH, ce qui signifie probablement que 48 n'est pas la bonne longueur de la structure. Je pense que c'est 44, mais vous devriez faire un sizeof pour être sûr.

7

Pour supprimer une arborescence de processus sous Windows, seul le processus parent ou l'ID de processus doit être indiqué, vous devez parcourir l'arborescence de processus.

Pour cela, vous aurez besoin d'un moyen d'obtenir l'identifiant du processus parent pour un processus donné.

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

Une fois que vous avez cela, tuer réellement l'arbre n'est pas difficile.

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

J'ai essayé le code ci-dessus et en effet, cela ne fonctionne pas, se plaignant d'une mauvaise taille.La raison en est que la structure utilisée change de taille en fonction de la plate-forme hôte; le fragment de code original (vu sur une douzaine de sites Web) suppose une application 32 bits.

Basculez la structure vers ceci (notez les membres de redimensionnement IntPtr) et cela fonctionnera. Au moins, ça l'a fait pour moi.

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