2010-07-13 26 views
14

Attività: Elimina automaticamente tutti i processi figlio se il processo padre termina. Gli approvvigionamenti dei genitori possono essere interrotti non solo in modo corretto, ma anche eliminando ProcessExplorer, ad esempio. Come posso farlo?Come terminare i processi secondari quando il processo padre termina in C#

domanda simile in С topic consigli per utilizzare oggetti di lavoro. Come usarlo in C# senza esportare DLL esterne?


Ho provato a utilizzare Oggetti lavoro. Ma questo codice non funziona correttamente:

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 ritorna con l'errore. GetLastError restituisce l'errore 24. Tuttavia, PInvoke.AssignProcessToJobObject funziona e il processo figlio viene aggiunto a Coda lavori (lo si può vedere in ProcessExplorer). Ma, dato che PInvoke.SetInformationJobObject non funziona, il processo generato rimane attivo quando uccido un genitore.

Che cosa ho errato in questo codice?

+0

L'altra domanda di risposta sembra essere buona con me, solo pinvoke le funzioni dal kernel32. http://www.pinvoke.net/default.aspx/kernel32.assignprocesstojobobject –

risposta

4

È possibile passare ProcessID del processo padre come argomento al processo figlio. E poi i processi figli saranno responsabili di controllare di volta in volta se il processo genitore è ancora in esecuzione. (Chiamando Process.GetProcessById.)

Un altro modo per tenere traccia dell'esistenza del processo padre consiste nell'utilizzare la primitiva di sincronizzazione Mutex. L'applicazione padre creerà inizialmente un mutex globale con il nome conosciuto dai bambini. I bambini possono controllare di tanto in tanto se il mutex esiste ancora e terminare in caso contrario. (Una volta chiuso il processo genitore, il mutex verrà distrutto automaticamente dal sistema, indipendentemente dal modo in cui è chiuso.)

+1

Entrambi i consigli non sono utili - i processi figli non sono miei. Possono essere tutti i programmi. – LionSoft

+2

@LionSoft: puoi avere un altro processo figlio che sarà responsabile della creazione di quei processi figli? Quindi quel processo può controllare se il processo genitore è ancora in esecuzione e uccidere altri bambini se non lo sono. – Regent

+0

Ma cosa fare se quel "altro processo figlio" sarà forzato? – LionSoft

2

Windows non forza la chiusura dei processi figli quando si chiude un processo padre. Quando selezioni "Kill Tree" in uno strumento come Task Manager o Process explorer, lo strumento trova effettivamente tutti i processi figli e li uccide uno per uno.

Se si desidera garantire che i processi figlio vengano puliti quando termina l'applicazione, è possibile creare una classe ProcessManager che implementa IDisposable che crea effettivamente i processi, tiene traccia delle loro istanze e chiama Kill su ognuno di essi su Dispose, per esempio

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

Sfortunatamente, quando non riesco a eliminare il processo in ProcessExplorer, il processo non ha alcuna possibilità di eseguire il codice di finalizzazione. Quindi, il tuo esemplare funzionerà solo quando il processo genitore terminerà correttamente. BTW, per correggere il tuo lavoro, devi aggiungere la riga ** newProcess.EnableRaisingEvents = true; ** prima di assegnare l'evento * Exiting *. – LionSoft

+2

Come ho già detto, Windows non uccide i processi figli quando un processo genitore muore. Non esiste un meccanismo OS per far rispettare questo. Un bambino proces NON appartiene al suo genitore. Se si desidera generare processi di elaborazione che sono garantiti per essere puliti quando un processo muore, è necessario utilizzare i thread. Hai ragione su EnableRaisingEvents, risolto. –

+2

Esistono almeno due meccanismi del sistema operativo per eliminare i processi generati: 1. Collegarsi al processo figlio come debugger. 2. Usa oggetti lavoro con JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE flag Ma non riesco a utilizzare entrambi i metodi finora. :( – LionSoft

3

Avete prestato attenzione al codice di errore? L'errore 24 è ERROR_BAD_LENGTH, il che probabilmente significa che 48 non è la lunghezza corretta della struttura. Penso che sia 44, ma dovresti fare uno sizeof per essere sicuro.

7

Per uccidere un albero del processo su Windows, dato solo il processo genitore o l'id del processo, è necessario percorrere l'albero del processo.

Per questo, è necessario un modo per ottenere l'id del processo principale per un determinato processo.

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 volta ottenuto ciò, uccidere effettivamente l'albero non è 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

Ho provato il codice sopra e, in effetti, non funziona, lamentando una cattiva dimensione.La ragione di ciò è che la struttura utilizzata modifica le dimensioni in base alla piattaforma host; il frammento di codice originale (visto su una dozzina di siti Web) assume un'applicazione a 32 bit.

Passare alla struttura (notare i membri di ridimensionamento IntPtr) e funzionerà. Almeno lo ha fatto per me.

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