Termina il processo figlio quando viene interrotto il processo padre


156

Sto creando nuovi processi usando la System.Diagnostics.Processclasse dalla mia applicazione.

Voglio che questi processi vengano interrotti quando / se la mia applicazione è andata in crash. Ma se uccido la mia applicazione da Task Manager, i processi figlio non vengono uccisi.

C'è un modo per rendere i processi figlio dipendenti dal processo genitore?

Risposte:


176

Da questo forum , merito a "Josh".

Application.Quit()e Process.Kill()sono possibili soluzioni, ma si sono dimostrate inaffidabili. Quando l'applicazione principale muore, rimangono ancora processi secondari in esecuzione. Ciò che vogliamo veramente è che i processi figlio muoiano non appena muore il processo principale.

La soluzione consiste nell'utilizzare "oggetti processo" http://msdn.microsoft.com/en-us/library/ms682409(VS.85).aspx .

L'idea è quella di creare un "oggetto lavoro" per l'applicazione principale e registrare i processi figlio con l'oggetto lavoro. Se il processo principale muore, il sistema operativo si occuperà di terminare i processi figlio.

public enum JobObjectInfoType
{
    AssociateCompletionPortInformation = 7,
    BasicLimitInformation = 2,
    BasicUIRestrictions = 4,
    EndOfJobTimeInformation = 6,
    ExtendedLimitInformation = 9,
    SecurityLimitInformation = 5,
    GroupInformation = 11
}

[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
    public int nLength;
    public IntPtr lpSecurityDescriptor;
    public int bInheritHandle;
}

[StructLayout(LayoutKind.Sequential)]
struct JOBOBJECT_BASIC_LIMIT_INFORMATION
{
    public Int64 PerProcessUserTimeLimit;
    public Int64 PerJobUserTimeLimit;
    public Int16 LimitFlags;
    public UInt32 MinimumWorkingSetSize;
    public UInt32 MaximumWorkingSetSize;
    public Int16 ActiveProcessLimit;
    public Int64 Affinity;
    public Int16 PriorityClass;
    public Int16 SchedulingClass;
}

[StructLayout(LayoutKind.Sequential)]
struct IO_COUNTERS
{
    public UInt64 ReadOperationCount;
    public UInt64 WriteOperationCount;
    public UInt64 OtherOperationCount;
    public UInt64 ReadTransferCount;
    public UInt64 WriteTransferCount;
    public UInt64 OtherTransferCount;
}

[StructLayout(LayoutKind.Sequential)]
struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION
{
    public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
    public IO_COUNTERS IoInfo;
    public UInt32 ProcessMemoryLimit;
    public UInt32 JobMemoryLimit;
    public UInt32 PeakProcessMemoryUsed;
    public UInt32 PeakJobMemoryUsed;
}

public class Job : IDisposable
{
    [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
    static extern IntPtr CreateJobObject(object a, string lpName);

    [DllImport("kernel32.dll")]
    static extern bool SetInformationJobObject(IntPtr hJob, JobObjectInfoType infoType, IntPtr lpJobObjectInfo, uint cbJobObjectInfoLength);

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process);

    private IntPtr m_handle;
    private bool m_disposed = false;

    public Job()
    {
        m_handle = CreateJobObject(null, null);

        JOBOBJECT_BASIC_LIMIT_INFORMATION info = new JOBOBJECT_BASIC_LIMIT_INFORMATION();
        info.LimitFlags = 0x2000;

        JOBOBJECT_EXTENDED_LIMIT_INFORMATION extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION();
        extendedInfo.BasicLimitInformation = info;

        int length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
        IntPtr extendedInfoPtr = Marshal.AllocHGlobal(length);
        Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false);

        if (!SetInformationJobObject(m_handle, JobObjectInfoType.ExtendedLimitInformation, extendedInfoPtr, (uint)length))
            throw new Exception(string.Format("Unable to set information.  Error: {0}", Marshal.GetLastWin32Error()));
    }

    #region IDisposable Members

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    #endregion

    private void Dispose(bool disposing)
    {
        if (m_disposed)
            return;

        if (disposing) {}

        Close();
        m_disposed = true;
    }

    public void Close()
    {
        Win32.CloseHandle(m_handle);
        m_handle = IntPtr.Zero;
    }

    public bool AddProcess(IntPtr handle)
    {
        return AssignProcessToJobObject(m_handle, handle);
    }

}

Guardando il costruttore ...

JOBOBJECT_BASIC_LIMIT_INFORMATION info = new JOBOBJECT_BASIC_LIMIT_INFORMATION();
info.LimitFlags = 0x2000;

La chiave qui è di impostare correttamente l'oggetto lavoro. Nel costruttore sto impostando i "limiti" su 0x2000, che è il valore numerico per JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE.

MSDN definisce questo flag come:

Fa terminare tutti i processi associati al lavoro quando viene chiuso l'ultimo handle per il lavoro.

Una volta impostata questa classe ... devi solo registrare ogni processo figlio con il lavoro. Per esempio:

[DllImport("user32.dll", SetLastError = true)]
public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);

Excel.Application app = new Excel.ApplicationClass();

uint pid = 0;
Win32.GetWindowThreadProcessId(new IntPtr(app.Hwnd), out pid);
 job.AddProcess(Process.GetProcessById((int)pid).Handle);

4
Vorrei aggiungere un link a CloseHandle
Austin Salonen il

6
Sfortunatamente, non sono stato in grado di eseguirlo in modalità 64 bit. Qui ho pubblicato un esempio funzionante, che si basa su questo.
Alexander Yezutov,

2
@Matt Howells - Da dove viene Win32.CloseHandle? Questo è importato da kernel32.dll? C'è una firma corrispondente lì, ma non la stai importando esplicitamente come le altre funzioni API.
Nome schermo esoterico

6
Per app in modalità 64 bit -> stackoverflow.com/a/5976162 Per il problema Vista / Win7 -> social.msdn.microsoft.com/forums/en-US/windowssecurity/thread/…
hB0

3
Ok, MSDN dice: Il flag JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE richiede l'uso di una struttura JOBOBJECT_EXTENDED_LIMIT_INFORMATION.
SerG

54

Questa risposta è iniziata con l'eccellente risposta di @Matt Howells e altre (vedere i collegamenti nel codice seguente). miglioramenti:

  • Supporta 32 bit e 64 bit.
  • Risolve alcuni problemi nella risposta di @Matt Howells:
    1. La piccola perdita di memoria di extendedInfoPtr
    2. L'errore di compilazione "Win32" e
    3. Un'eccezione sbilanciata dallo stack a cui ho ricevuto la chiamata CreateJobObject(utilizzando Windows 10, Visual Studio 2015, 32 bit).
  • Assegna un nome al lavoro, quindi se usi SysInternals, ad esempio, puoi trovarlo facilmente.
  • Ha un'API un po 'più semplice e meno codice.

Ecco come utilizzare questo codice:

// Get a Process object somehow.
Process process = Process.Start(exePath, args);
// Add the Process to ChildProcessTracker.
ChildProcessTracker.AddProcess(process);

Per supportare Windows 7 è necessario:

Nel mio caso, non avevo bisogno di supportare Windows 7, quindi ho un semplice controllo nella parte superiore del costruttore statico di seguito.

/// <summary>
/// Allows processes to be automatically killed if this parent process unexpectedly quits.
/// This feature requires Windows 8 or greater. On Windows 7, nothing is done.</summary>
/// <remarks>References:
///  https://stackoverflow.com/a/4657392/386091
///  https://stackoverflow.com/a/9164742/386091 </remarks>
public static class ChildProcessTracker
{
    /// <summary>
    /// Add the process to be tracked. If our current process is killed, the child processes
    /// that we are tracking will be automatically killed, too. If the child process terminates
    /// first, that's fine, too.</summary>
    /// <param name="process"></param>
    public static void AddProcess(Process process)
    {
        if (s_jobHandle != IntPtr.Zero)
        {
            bool success = AssignProcessToJobObject(s_jobHandle, process.Handle);
            if (!success && !process.HasExited)
                throw new Win32Exception();
        }
    }

    static ChildProcessTracker()
    {
        // This feature requires Windows 8 or later. To support Windows 7 requires
        //  registry settings to be added if you are using Visual Studio plus an
        //  app.manifest change.
        //  https://stackoverflow.com/a/4232259/386091
        //  https://stackoverflow.com/a/9507862/386091
        if (Environment.OSVersion.Version < new Version(6, 2))
            return;

        // The job name is optional (and can be null) but it helps with diagnostics.
        //  If it's not null, it has to be unique. Use SysInternals' Handle command-line
        //  utility: handle -a ChildProcessTracker
        string jobName = "ChildProcessTracker" + Process.GetCurrentProcess().Id;
        s_jobHandle = CreateJobObject(IntPtr.Zero, jobName);

        var info = new JOBOBJECT_BASIC_LIMIT_INFORMATION();

        // This is the key flag. When our process is killed, Windows will automatically
        //  close the job handle, and when that happens, we want the child processes to
        //  be killed, too.
        info.LimitFlags = JOBOBJECTLIMIT.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;

        var extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION();
        extendedInfo.BasicLimitInformation = info;

        int length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
        IntPtr extendedInfoPtr = Marshal.AllocHGlobal(length);
        try
        {
            Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false);

            if (!SetInformationJobObject(s_jobHandle, JobObjectInfoType.ExtendedLimitInformation,
                extendedInfoPtr, (uint)length))
            {
                throw new Win32Exception();
            }
        }
        finally
        {
            Marshal.FreeHGlobal(extendedInfoPtr);
        }
    }

    [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
    static extern IntPtr CreateJobObject(IntPtr lpJobAttributes, string name);

    [DllImport("kernel32.dll")]
    static extern bool SetInformationJobObject(IntPtr job, JobObjectInfoType infoType,
        IntPtr lpJobObjectInfo, uint cbJobObjectInfoLength);

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process);

    // Windows will automatically close any open job handles when our process terminates.
    //  This can be verified by using SysInternals' Handle utility. When the job handle
    //  is closed, the child processes will be killed.
    private static readonly IntPtr s_jobHandle;
}

public enum JobObjectInfoType
{
    AssociateCompletionPortInformation = 7,
    BasicLimitInformation = 2,
    BasicUIRestrictions = 4,
    EndOfJobTimeInformation = 6,
    ExtendedLimitInformation = 9,
    SecurityLimitInformation = 5,
    GroupInformation = 11
}

[StructLayout(LayoutKind.Sequential)]
public struct JOBOBJECT_BASIC_LIMIT_INFORMATION
{
    public Int64 PerProcessUserTimeLimit;
    public Int64 PerJobUserTimeLimit;
    public JOBOBJECTLIMIT LimitFlags;
    public UIntPtr MinimumWorkingSetSize;
    public UIntPtr MaximumWorkingSetSize;
    public UInt32 ActiveProcessLimit;
    public Int64 Affinity;
    public UInt32 PriorityClass;
    public UInt32 SchedulingClass;
}

[Flags]
public enum JOBOBJECTLIMIT : uint
{
    JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x2000
}

[StructLayout(LayoutKind.Sequential)]
public struct IO_COUNTERS
{
    public UInt64 ReadOperationCount;
    public UInt64 WriteOperationCount;
    public UInt64 OtherOperationCount;
    public UInt64 ReadTransferCount;
    public UInt64 WriteTransferCount;
    public UInt64 OtherTransferCount;
}

[StructLayout(LayoutKind.Sequential)]
public struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION
{
    public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
    public IO_COUNTERS IoInfo;
    public UIntPtr ProcessMemoryLimit;
    public UIntPtr JobMemoryLimit;
    public UIntPtr PeakProcessMemoryUsed;
    public UIntPtr PeakJobMemoryUsed;
}

Ho testato attentamente le versioni a 32 e 64 bit delle strutture confrontando a livello di programmazione le versioni gestite e native (le dimensioni complessive e gli offset per ciascun membro).

Ho testato questo codice su Windows 7, 8 e 10.


che ne dici di chiudere la gestione del lavoro ??
Frank Q.

@FrankQ. È importante lasciare che Windows chiuda s_jobHandle per noi quando il nostro processo termina, perché il nostro processo potrebbe terminare in modo imprevisto (come un arresto anomalo o se l'utente utilizza Task Manager). Vedi il mio commento su s_jobHandle's.
Ron,

Funziona per me. Posso chiederti qual è la tua opinione sull'utilizzo della bandiera JOB_OBJECT_LIMIT_BREAKAWAY_OK? docs.microsoft.com/en-us/windows/desktop/api/winnt/…
Yiping

1
@yiping Sembra che CREATE_BREAKAWAY_FROM_JOB consente al processo figlio di generare un processo che può sopravvivere al processo originale. È un requisito diverso da quello richiesto dall'OP. Invece, se riesci a fare in modo che il tuo processo originale generi quel processo di lunga durata (e non usi ChildProcessTracker) sarebbe più semplice.
Ron,

@Ron Puoi aggiungere un sovraccarico, che accetta solo l'handle del processo e non l'intero processo?
Jannik,

47

Questo post è inteso come un'estensione della risposta di @Matt Howells, in particolare per coloro che incontrano problemi con l'utilizzo di Job Objects in Vista o Win7 , soprattutto se si riceve un errore di accesso negato ('5') quando si chiama AssignProcessToJobObject.

tl; dr

Per garantire la compatibilità con Vista e Win7, aggiungere il seguente manifest al processo padre .NET:

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  <v3:trustInfo xmlns:v3="urn:schemas-microsoft-com:asm.v3">
    <v3:security>
      <v3:requestedPrivileges>
        <v3:requestedExecutionLevel level="asInvoker" uiAccess="false" />
      </v3:requestedPrivileges>
    </v3:security>
  </v3:trustInfo>
  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
    <!-- We specify these, in addition to the UAC above, so we avoid Program Compatibility Assistant in Vista and Win7 -->
    <!-- We try to avoid PCA so we can use Windows Job Objects -->
    <!-- See https://stackoverflow.com/questions/3342941/kill-child-process-when-parent-process-is-killed -->

    <application>
      <!--The ID below indicates application support for Windows Vista -->
      <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
      <!--The ID below indicates application support for Windows 7 -->
      <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
    </application>
  </compatibility>
</assembly>

Si noti che quando si aggiunge un nuovo manifest in Visual Studio 2012 conterrà già lo snippet sopra indicato, quindi non è necessario copiarlo da hear. Includerà anche un nodo per Windows 8.

spiegazione completa

La tua associazione di lavoro fallirà con un errore di accesso negato se il processo che stai avviando è già associato a un altro lavoro. Immettere Program Compatibility Assistant, che, a partire da Windows Vista, assegnerà tutti i tipi di processi ai propri lavori.

In Vista puoi contrassegnare la tua applicazione come esclusa dal PCA semplicemente includendo un manifest dell'applicazione. Visual Studio sembra farlo automaticamente per le app .NET, quindi stai bene lì.

Un semplice manifest non lo taglia più in Win7. [1] Lì, devi specificare specificamente che sei compatibile con Win7 con il tag nel tuo manifest. [2]

Questo mi ha portato a preoccuparmi di Windows 8. Dovrò cambiare di nuovo il mio manifest? Apparentemente c'è una rottura tra le nuvole, poiché Windows 8 ora consente a un processo di appartenere a più lavori. [3] Quindi non l'ho ancora testato, ma immagino che questa follia finirà ora se includi semplicemente un manifest con le informazioni supportate su OS.

Suggerimento 1 : se stai sviluppando un'app .NET con Visual Studio, com'ero, ecco [4] alcune belle istruzioni su come personalizzare il manifest dell'applicazione.

Suggerimento 2 : prestare attenzione all'avvio dell'applicazione da Visual Studio. Ho scoperto che, dopo aver aggiunto il manifest appropriato, avevo ancora problemi con PCA all'avvio da Visual Studio, anche se ho usato Avvia senza debug. Avvio della mia applicazione da Explorer ha funzionato, tuttavia. Dopo aver aggiunto manualmente devenv per l'esclusione da PCA utilizzando il registro, anche le applicazioni di avvio che hanno utilizzato Job Objects da VS hanno iniziato a funzionare. [5]

Suggerimento 3 : se mai vuoi sapere se il tuo PCA è il tuo problema, prova ad avviare l'applicazione dalla riga di comando o copia il programma su un'unità di rete ed eseguilo da lì. La PCA viene automaticamente disabilitata in tali contesti.

[1] http://blogs.msdn.com/b/cjacks/archive/2009/06/18/pca-changes-for-windows-7-how-to-tell-us-you-are-not-an -installer-take-2-perchè-ci-le-regole-on-you.aspx cambiato-

[2] http://ayende.com/blog/4360/how-to-opt-out-of-program-compatibility-assistant

[3] http://msdn.microsoft.com/en-us/library/windows/desktop/ms681949(v=vs.85).aspx : "Un processo può essere associato a più di un lavoro in Windows 8"

[4] Come posso incorporare un manifest in un'applicazione usando VS2008?

[5] Come impedire al debugger di Visual Studio di avviare il mio processo in un oggetto lavoro?


2
Queste sono ottime aggiunte all'argomento, grazie! Ho fatto uso di ogni aspetto di questa risposta, compresi i collegamenti.
Johnny Kauffman,

16

Ecco un'alternativa che potrebbe funzionare per alcuni quando si ha il controllo del codice eseguito dal processo figlio. Il vantaggio di questo approccio è che non richiede chiamate Windows native.

L'idea di base è reindirizzare l'input standard del figlio su uno stream la cui altra estremità è connessa al genitore e utilizzare quel flusso per rilevare quando il genitore è andato via. Quando si utilizza System.Diagnostics.Processper avviare il figlio, è facile assicurarsi che il suo input standard venga reindirizzato:

Process childProcess = new Process();
childProcess.StartInfo = new ProcessStartInfo("pathToConsoleModeApp.exe");
childProcess.StartInfo.RedirectStandardInput = true;

childProcess.StartInfo.CreateNoWindow = true; // no sense showing an empty black console window which the user can't input into

Quindi, nel processo figlio, approfitta del fatto che Reads dal flusso di input standard tornerà sempre con almeno 1 byte fino a quando il flusso non verrà chiuso, quando inizieranno a restituire 0 byte. Di seguito è riportato uno schema del modo in cui ho finito per farlo; il mio modo usa anche un pump dei messaggi per mantenere disponibile il thread principale per cose diverse dalla visione standard, ma questo approccio generale potrebbe essere usato anche senza pump dei messaggi.

using System;
using System.IO;
using System.Threading;
using System.Windows.Forms;

static int Main()
{
    Application.Run(new MyApplicationContext());
    return 0;
}

public class MyApplicationContext : ApplicationContext
{
    private SynchronizationContext _mainThreadMessageQueue = null;
    private Stream _stdInput;

    public MyApplicationContext()
    {
        _stdInput = Console.OpenStandardInput();

        // feel free to use a better way to post to the message loop from here if you know one ;)    
        System.Windows.Forms.Timer handoffToMessageLoopTimer = new System.Windows.Forms.Timer();
        handoffToMessageLoopTimer.Interval = 1;
        handoffToMessageLoopTimer.Tick += new EventHandler((obj, eArgs) => { PostMessageLoopInitialization(handoffToMessageLoopTimer); });
        handoffToMessageLoopTimer.Start();
    }

    private void PostMessageLoopInitialization(System.Windows.Forms.Timer t)
    {
        if (_mainThreadMessageQueue == null)
        {
            t.Stop();
            _mainThreadMessageQueue = SynchronizationContext.Current;
        }

        // constantly monitor standard input on a background thread that will
        // signal the main thread when stuff happens.
        BeginMonitoringStdIn(null);

        // start up your application's real work here
    }

    private void BeginMonitoringStdIn(object state)
    {
        if (SynchronizationContext.Current == _mainThreadMessageQueue)
        {
            // we're already running on the main thread - proceed.
            var buffer = new byte[128];

            _stdInput.BeginRead(buffer, 0, buffer.Length, (asyncResult) =>
                {
                    int amtRead = _stdInput.EndRead(asyncResult);

                    if (amtRead == 0)
                    {
                        _mainThreadMessageQueue.Post(new SendOrPostCallback(ApplicationTeardown), null);
                    }
                    else
                    {
                        BeginMonitoringStdIn(null);
                    }
                }, null);
        }
        else
        {
            // not invoked from the main thread - dispatch another call to this method on the main thread and return
            _mainThreadMessageQueue.Post(new SendOrPostCallback(BeginMonitoringStdIn), null);
        }
    }

    private void ApplicationTeardown(object state)
    {
        // tear down your application gracefully here
        _stdInput.Close();

        this.ExitThread();
    }
}

Avvertenze per questo approccio:

  1. l'attuale .exe figlio che viene avviato deve essere un'applicazione console, quindi rimane collegato a stdin / out / err. Come nell'esempio sopra, ho facilmente adattato la mia applicazione esistente che utilizzava un pump dei messaggi (ma non mostrava una GUI) semplicemente creando un piccolo progetto console che faceva riferimento al progetto esistente, creando un'istanza del contesto dell'applicazione e chiamando Application.Run()all'interno del Mainmetodo del console .exe.

  2. Tecnicamente, questo segnala semplicemente il processo figlio quando il genitore esce, quindi funzionerà se il processo genitore è uscito normalmente o si è arrestato in modo anomalo, ma è comunque compito dei processi figlio eseguire il proprio arresto. Questo può o meno essere quello che vuoi ...


11

Un modo è passare il PID del processo genitore al figlio. Il bambino eseguirà periodicamente il polling se esiste o meno il processo con il pid specificato. In caso contrario, si chiuderà.

È inoltre possibile utilizzare il metodo Process.WaitForExit nel metodo figlio per ricevere una notifica al termine del processo principale, ma potrebbe non funzionare in caso di Task Manager.


Come posso passare il PID genitore al figlio? C'è qualche soluzione di sistema? Non riesco a modificare i file binari del processo figlio.
SiberianGuy

1
Bene, se non riesci a modificare il processo figlio non puoi usare la mia soluzione anche se ci passi PID.
Giorgi,

@Idsa Puoi passarlo dalla riga di comando:Process.Start(string fileName, string arguments)
Distortum

2
Anziché eseguire il polling, è possibile collegarsi all'evento Exit sulla classe di processo.
RichardOD,

L'ho provato ma il processo genitore non sempre esce se ha figli vivi (almeno nel mio caso Cobol-> NET). Facile da controllare guardando la gerarchia dei processi in Sysinternals ProcessExplorer.
Ivan Ferrer Villa,

8

Esiste un altro metodo pertinente, semplice ed efficace, per terminare i processi figlio al termine del programma. È possibile implementare e collegare un debugger a loro dal genitore; quando termina il processo padre, i processi figlio verranno eliminati dal sistema operativo. Può andare in entrambi i modi collegando un debugger al genitore dal figlio (si noti che è possibile collegare solo un debugger alla volta). Puoi trovare maggiori informazioni sull'argomento qui .

Qui hai una classe di utilità che avvia un nuovo processo e allega un debugger. È stato adattato da questo post di Roger Knapp. L'unico requisito è che entrambi i processi debbano condividere lo stesso testimone. Non è possibile eseguire il debug di un processo a 32 bit da un processo a 64 bit o viceversa.

public class ProcessRunner
{
    #region "API imports"

    private const int DBG_CONTINUE = 0x00010002;
    private const int DBG_EXCEPTION_NOT_HANDLED = unchecked((int) 0x80010001);

    private enum DebugEventType : int
    {
        CREATE_PROCESS_DEBUG_EVENT = 3,
        //Reports a create-process debugging event. The value of u.CreateProcessInfo specifies a CREATE_PROCESS_DEBUG_INFO structure.
        CREATE_THREAD_DEBUG_EVENT = 2,
        //Reports a create-thread debugging event. The value of u.CreateThread specifies a CREATE_THREAD_DEBUG_INFO structure.
        EXCEPTION_DEBUG_EVENT = 1,
        //Reports an exception debugging event. The value of u.Exception specifies an EXCEPTION_DEBUG_INFO structure.
        EXIT_PROCESS_DEBUG_EVENT = 5,
        //Reports an exit-process debugging event. The value of u.ExitProcess specifies an EXIT_PROCESS_DEBUG_INFO structure.
        EXIT_THREAD_DEBUG_EVENT = 4,
        //Reports an exit-thread debugging event. The value of u.ExitThread specifies an EXIT_THREAD_DEBUG_INFO structure.
        LOAD_DLL_DEBUG_EVENT = 6,
        //Reports a load-dynamic-link-library (DLL) debugging event. The value of u.LoadDll specifies a LOAD_DLL_DEBUG_INFO structure.
        OUTPUT_DEBUG_STRING_EVENT = 8,
        //Reports an output-debugging-string debugging event. The value of u.DebugString specifies an OUTPUT_DEBUG_STRING_INFO structure.
        RIP_EVENT = 9,
        //Reports a RIP-debugging event (system debugging error). The value of u.RipInfo specifies a RIP_INFO structure.
        UNLOAD_DLL_DEBUG_EVENT = 7,
        //Reports an unload-DLL debugging event. The value of u.UnloadDll specifies an UNLOAD_DLL_DEBUG_INFO structure.
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct DEBUG_EVENT
    {
        [MarshalAs(UnmanagedType.I4)] public DebugEventType dwDebugEventCode;
        public int dwProcessId;
        public int dwThreadId;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1024)] public byte[] bytes;
    }

    [DllImport("Kernel32.dll", SetLastError = true)]
    private static extern bool DebugActiveProcess(int dwProcessId);

    [DllImport("Kernel32.dll", SetLastError = true)]
    private static extern bool WaitForDebugEvent([Out] out DEBUG_EVENT lpDebugEvent, int dwMilliseconds);

    [DllImport("Kernel32.dll", SetLastError = true)]
    private static extern bool ContinueDebugEvent(int dwProcessId, int dwThreadId, int dwContinueStatus);

    [DllImport("Kernel32.dll", SetLastError = true)]
    public static extern bool IsDebuggerPresent();

    #endregion

    public Process ChildProcess { get; set; }

    public bool StartProcess(string fileName)
    {
        var processStartInfo = new ProcessStartInfo(fileName)
        {
            UseShellExecute = false,
            WindowStyle = ProcessWindowStyle.Normal,
            ErrorDialog = false
        };

        this.ChildProcess = Process.Start(processStartInfo);
        if (ChildProcess == null)
            return false;

        new Thread(NullDebugger) {IsBackground = true}.Start(ChildProcess.Id);
        return true;
    }

    private void NullDebugger(object arg)
    {
        // Attach to the process we provided the thread as an argument
        if (DebugActiveProcess((int) arg))
        {
            var debugEvent = new DEBUG_EVENT {bytes = new byte[1024]};
            while (!this.ChildProcess.HasExited)
            {
                if (WaitForDebugEvent(out debugEvent, 1000))
                {
                    // return DBG_CONTINUE for all events but the exception type
                    var continueFlag = DBG_CONTINUE;
                    if (debugEvent.dwDebugEventCode == DebugEventType.EXCEPTION_DEBUG_EVENT)
                        continueFlag = DBG_EXCEPTION_NOT_HANDLED;
                    ContinueDebugEvent(debugEvent.dwProcessId, debugEvent.dwThreadId, continueFlag);
                }
            }
        }
        else
        {
            //we were not able to attach the debugger
            //do the processes have the same bitness?
            //throw ApplicationException("Unable to attach debugger") // Kill child? // Send Event? // Ignore?
        }
    }
}

Uso:

    new ProcessRunner().StartProcess("c:\\Windows\\system32\\calc.exe");

8

Stavo cercando una soluzione a questo problema che non richiedeva codice non gestito. Inoltre, non ero in grado di utilizzare il reindirizzamento input / output standard perché era un'applicazione Windows Form.

La mia soluzione era quella di creare una pipa denominata nel processo padre e quindi collegare il processo figlio alla stessa pipa. Se il processo padre viene chiuso, la pipe si rompe e il bambino può rilevarlo.

Di seguito è riportato un esempio che utilizza due applicazioni console:

Genitore

private const string PipeName = "471450d6-70db-49dc-94af-09d3f3eba529";

public static void Main(string[] args)
{
    Console.WriteLine("Main program running");

    using (NamedPipeServerStream pipe = new NamedPipeServerStream(PipeName, PipeDirection.Out))
    {
        Process.Start("child.exe");

        Console.WriteLine("Press any key to exit");
        Console.ReadKey();
    }
}

Bambino

private const string PipeName = "471450d6-70db-49dc-94af-09d3f3eba529"; // same as parent

public static void Main(string[] args)
{
    Console.WriteLine("Child process running");

    using (NamedPipeClientStream pipe = new NamedPipeClientStream(".", PipeName, PipeDirection.In))
    {
        pipe.Connect();
        pipe.BeginRead(new byte[1], 0, 1, PipeBrokenCallback, pipe);

        Console.WriteLine("Press any key to exit");
        Console.ReadKey();
    }
}

private static void PipeBrokenCallback(IAsyncResult ar)
{
    // the pipe was closed (parent process died), so exit the child process too

    try
    {
        NamedPipeClientStream pipe = (NamedPipeClientStream)ar.AsyncState;
        pipe.EndRead(ar);
    }
    catch (IOException) { }

    Environment.Exit(1);
}

3

Utilizzare i gestori di eventi per agganciare alcuni scenari di uscita:

var process = Process.Start("program.exe");
AppDomain.CurrentDomain.DomainUnload += (s, e) => { process.Kill(); process.WaitForExit(); };
AppDomain.CurrentDomain.ProcessExit += (s, e) => { process.Kill(); process.WaitForExit(); };
AppDomain.CurrentDomain.UnhandledException += (s, e) => { process.Kill(); process.WaitForExit(); };

Così semplice, eppure così efficace.
uncommon_name

2

Solo la mia versione 2018. Usalo a parte il tuo metodo Main ().

    using System.Management;
    using System.Diagnostics;

    ...

    // Called when the Main Window is closed
    protected override void OnClosed(EventArgs EventArgs)
    {
        string query = "Select * From Win32_Process Where ParentProcessId = " + Process.GetCurrentProcess().Id;
        ManagementObjectSearcher searcher = new ManagementObjectSearcher(query);
        ManagementObjectCollection processList = searcher.Get();
        foreach (var obj in processList)
        {
            object data = obj.Properties["processid"].Value;
            if (data != null)
            {
                // retrieve the process
                var childId = Convert.ToInt32(data);
                var childProcess = Process.GetProcessById(childId);

                // ensure the current process is still live
                if (childProcess != null) childProcess.Kill();
            }
        }
        Environment.Exit(0);
    }

4
Dubito che questo venga eseguito quando il processo genitore viene ucciso .
springy76

1

Vedo due opzioni:

  1. Se sai esattamente quale processo figlio potrebbe essere avviato e sei sicuro che siano stati avviati solo dal processo principale, allora potresti considerare semplicemente di cercarli per nome e ucciderli.
  2. Scorri attraverso tutti i processi e uccidi tutti i processi che hanno il tuo processo come genitore (immagino che tu debba prima uccidere i processi figlio). Di seguito viene spiegato come ottenere l'ID del processo principale.

1

Ho creato una libreria di gestione dei processi figlio in cui il processo padre e il processo figlio sono monitorati a causa di una pipe bidirezionale WCF. Se il processo figlio termina o il processo padre termina a vicenda viene notificato. È inoltre disponibile un supporto per il debugger che collega automaticamente il debugger VS al processo figlio avviato

Sito del progetto:

http://www.crawler-lib.net/child-processes

Pacchetti NuGet:

https://www.nuget.org/packages/ChildProcesses https://www.nuget.org/packages/ChildProcesses.VisualStudioDebug/


0

chiamare job.AddProcess meglio fare dopo l'avvio del processo:

prc.Start();
job.AddProcess(prc.Handle);

Quando si chiama AddProcess prima della chiusura, i processi figlio non vengono uccisi. (Windows 7 SP1)

private void KillProcess(Process proc)
{
    var job = new Job();
    job.AddProcess(proc.Handle);
    job.Close();
}

La chiamata di job.AddProcess dopo l'avvio del processo ucciderebbe lo scopo di utilizzare questo oggetto.
SerG

0

Ennesima aggiunta all'abbondante ricchezza di soluzioni proposte finora ...

Il problema con molti di loro è che fanno affidamento sul processo genitore e figlio per chiudere in modo ordinato, il che non è sempre vero quando lo sviluppo è in corso. Ho scoperto che il mio processo figlio veniva spesso reso orfano ogni volta che terminavo il processo genitore nel debugger, il che mi richiedeva di terminare i processi orfani con Task Manager per ricostruire la mia soluzione.

La soluzione: passare l'ID del processo parent nella riga di comando (o anche meno invasivo, nelle variabili di ambiente) del processo child.

Nel processo padre, l'ID processo è disponibile come:

  Process.CurrentProcess.Id;

Nel processo figlio:

Process parentProcess = Process.GetProcessById(parentProcessId);
parentProcess.Exited += (s, e) =>
{
    // clean up what you can.
    this.Dispose();
    // maybe log an error
    ....

    // And terminate with prejudice! 
    //(since something has already gone terribly wrong)
    Process.GetCurrentProcess().Kill();
}

Ho due idee sul fatto che questa sia una pratica accettabile nel codice di produzione. Da un lato, ciò non dovrebbe mai accadere. D'altra parte, potrebbe significare la differenza tra il riavvio di un processo e il riavvio di un server di produzione. E ciò che non dovrebbe mai accadere spesso lo fa.

Ed è sicuramente utile durante il debug di problemi di arresto ordinato.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.