Töte den Child-process, wenn der Elternprozess beendet wird

Ich erstelle neue processe mit der System.Diagnostics.Process class aus meiner Anwendung.

Ich möchte, dass diese processe beendet werden, wenn meine Anwendung abgestürzt ist. Aber wenn ich meine Anwendung vom Task-Manager abbringe, werden untergeordnete processe nicht gelöscht.

Gibt es eine Möglichkeit, Kindprozesse vom Elternprozess abhängig zu machen?

Aus diesem Forum , Kredit für “Josh”.

Application.Quit() und Process.Kill() sind mögliche Lösungen, haben sich aber als unzuverlässig erwiesen. Wenn die Hauptanwendung beendet wird, bleiben die untergeordneten processe aktiv. Was wir wirklich wollen, ist, dass die Kindprozesse sterben, sobald der Hauptprozess stirbt.

Die Lösung besteht darin, “Jobobjekte” http://msdn.microsoft.com/en-us/library/ms682409(VS.85).aspx zu verwenden .

Die Idee ist, ein “Job-Objekt” für Ihre Hauptanwendung zu erstellen und Ihre untergeordneten processe mit dem Job-Objekt zu registrieren. Wenn der Hauptprozess abbricht, kümmert sich das Betriebssystem darum, die untergeordneten processe zu beenden.

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

Blick auf den Konstruktor …

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

Der Schlüssel hier ist, das Job-Objekt richtig einzurichten. Im Konstruktor setze ich die “Grenzen” auf 0x2000, was der numerische Wert für JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE .

MSDN definiert dieses Flag als:

Bewirkt, dass alle mit dem Job verknüpften processe beendet werden, wenn das letzte Handle für den Job geschlossen wird.

Sobald diese class eingerichtet ist, müssen Sie nur jeden untergeordneten process für den Job registrieren. Beispielsweise:

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

Dieser Beitrag ist als eine Erweiterung der Antwort von @Matt Howells gedacht, speziell für diejenigen, die Probleme mit der Verwendung von Jobobjekten unter Vista oder Win7 haben , insbesondere wenn Sie beim Aufruf von AssignProcessToJobObject einen Zugriffsverweigerungserrors (‘5’) erhalten.

tl; dr

Um die Kompatibilität mit Vista und Win7 zu gewährleisten, fügen Sie dem übergeordneten .NET-process das folgende Manifest hinzu:

 < ?xml version="1.0" encoding="utf-8" standalone="yes"?>                     

Beachten Sie, dass beim Hinzufügen eines neuen Manifests in Visual Studio 2012 das obige Snippet bereits enthalten ist, sodass Sie es nicht von hear kopieren müssen. Es wird auch einen Knoten für Windows 8 enthalten.

vollständige Erklärung

Ihre Jobverknüpfung schlägt mit einem Zugriff verweigert Fehler fehl, wenn der process, den Sie starten, bereits einem anderen Job zugeordnet ist. Geben Sie den Programmkompatibilitäts-Assistenten ein, der ab Windows Vista alle Arten von processen seinen eigenen Jobs zuweist.

In Vista können Sie Ihre Anwendung so kennzeichnen, dass sie von PCA ausgeschlossen wird, indem Sie einfach ein Anwendungsmanifest hinzufügen. Visual Studio scheint dies für .NET-Anwendungen automatisch zu tun, also geht es Ihnen gut.

Ein einfaches Manifest schneidet es in Win7 nicht mehr ab. [1] Dort müssen Sie speziell angeben, dass Sie mit dem Tag in Ihrem Manifest kompatibel zu Win7 sind. [2]

Das hat mich dazu gebracht, mich um Windows 8 zu sorgen. Muss ich mein Manifest noch einmal ändern? Anscheinend gibt es eine Pause in den Wolken, da Windows 8 es nun zulässt, dass ein process zu mehreren Jobs gehört. [3] Also habe ich es noch nicht getestet, aber ich stelle mir vor, dass dieser Wahnsinn jetzt vorbei ist, wenn Sie einfach ein Manifest mit den unterstütztenOS-Informationen einschließen.

Tipp 1 : Wenn Sie wie ich eine .NET-Anwendung mit Visual Studio entwickeln, finden Sie hier [4] einige nützliche statementen zum Anpassen des Anwendungsmanifests.

Tipp 2 : Seien Sie vorsichtig beim Starten Ihrer Anwendung von Visual Studio. Ich habe festgestellt, dass nach dem Hinzufügen des entsprechenden Manifests beim Starten von Visual Studio immer noch Probleme mit PCA auftraten, selbst wenn ich Start ohne Debugging verwendete. Das Starten meiner Anwendung aus dem Explorer funktionierte jedoch. Nach dem manuellen Hinzufügen von devenv zum Ausschluss von PCA mit der Registrierung, begann das Starten von Anwendungen, die Job Objects von VS verwendeten, ebenfalls. [5]

Tipp 3 : Wenn Sie jemals wissen wollen, ob PCA Ihr Problem ist, starten Sie Ihre Anwendung über die Befehlszeile oder kopieren Sie das Programm auf ein Netzlaufwerk und führen Sie es von dort aus. PCA wird in diesen Kontexten automatisch deaktiviert.

[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-weil-wir-geändert-die-Regeln-auf-you.aspx

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

[3] http://msdn.microsoft.com/en-us/library/windows/desktop/ms681949(v=vs.85).aspx : “Ein process kann mehreren Jobs in Windows 8 zugeordnet werden”

[4] Wie kann ich ein Anwendungsmanifest mit VS2008 in eine Anwendung einbetten?

[5] Wie kann ich den Visual Studio-Debugger stoppen, der meinen process in einem Jobobjekt startet?

Diese Antwort begann mit der ausgezeichneten Antwort von @Matt Howells und anderen (siehe Links im folgenden Code). Verbesserungen:

  • Unterstützt 32-Bit und 64-Bit.
  • Behebt einige Probleme in @Matt Howells ‘Antwort:
    1. Das kleine Speicherleck von extendedInfoPtr
    2. Der ‘Win32’-Kompiliererrors und
    3. Eine stack-unsymmetrische Ausnahme, die ich beim Aufruf von CreateJobObject (unter Verwendung von Windows 10, Visual Studio 2015, 32-Bit).
  • Benennt den Job. Wenn Sie beispielsweise SysInternals verwenden, können Sie ihn leicht finden.
  • Hat eine etwas einfachere API und weniger Code.

So verwenden Sie diesen Code:

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

Um Windows 7 zu unterstützen, benötigen Sie:

  • Eine einfache app.manifest Änderung wie @adam Smith beschreibt .
  • Registrierungseinstellungen, die hinzugefügt werden sollen, wenn Sie Visual Studio verwenden.

In meinem Fall musste ich Windows 7 nicht unterstützen, daher habe ich oben im statischen Konstruktor eine einfache Überprüfung.

 ///  /// 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. /// References: /// https://stackoverflow.com/a/4657392/386091 /// https://stackoverflow.com/a/9164742/386091  public static class ChildProcessTracker { ///  /// 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. ///  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; } 

Ich habe sowohl die 32-Bit- als auch die 64-Bit-Versionen der Strukturen sorgfältig getestet, indem ich die verwalteten und nativen Versionen programmatisch miteinander verglichen habe (die Gesamtgröße sowie die Offsets für jedes Mitglied).

Ich habe diesen Code unter Windows 7, 8 und 10 getestet.

Hier ist eine Alternative, die für einige funktionieren kann, wenn Sie die Kontrolle über den Code haben, den der Kindprozess ausführt. Der Vorteil dieses Ansatzes ist, dass keine systemeigenen Windows-Aufrufe erforderlich sind.

Die Grundidee besteht darin, die Standardeingabe des Kindes in einen Stream umzuleiten, dessen anderes Ende mit dem Elternteil verbunden ist, und diesen Stream zu verwenden, um zu erkennen, wann das Elternteil gegangen ist. Wenn Sie System.Diagnostics.Process , um das System.Diagnostics.Process zu starten, können Sie einfach sicherstellen, dass die Standardeingabe umgeleitet wird:

 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 

Nutzen Sie dann im Child-process die Tatsache, dass Read s aus dem Standard-Eingabestream immer mit mindestens 1 Byte zurückkehrt, bis der Stream geschlossen wird, und dann 0 Bytes zurückgibt. Ein Überblick darüber, wie ich das gemacht habe, ist unten; Mein Weg verwendet auch eine Nachrichtenpumpe, um den Hauptthread für andere Dinge verfügbar zu halten, als Standard zu beobachten, aber dieser allgemeine Ansatz könnte auch ohne Nachrichtenpumpen verwendet werden.

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

Vorbehalte zu diesem Ansatz:

  1. Die tatsächliche untergeordnete .exe, die gestartet wird, muss eine Konsolenanwendung sein, sodass sie an stdin / out / err angehängt bleibt. Wie im obigen Beispiel habe ich meine vorhandene Anwendung, die einen Message-Pump verwendet (ohne GUI anzuzeigen), problemlos angepasst, indem ich ein kleines Konsolenprojekt erstellt habe, das auf das vorhandene Projekt verweist, meinen Anwendungskontext instanziiert und Application.Run() aufruft. innerhalb der Main Methode der Konsole .exe.

  2. Technisch signalisiert dies lediglich den Kindprozess, wenn das Elternteil beendet wird, so dass es funktionieren wird, ob der Elternprozess normal beendet oder abgestürzt ist, aber es immer noch an den Kindprozessen liegt, sein eigenes Herunterfahren durchzuführen. Dies kann oder kann nicht sein, was Sie wollen …

Eine Möglichkeit besteht darin, die PID des Elternprozesses an das Kind zu übergeben. Das Kind wird regelmäßig abfragen, ob der process mit der angegebenen PID existiert oder nicht. Wenn nicht, wird es einfach aufhören.

Sie können auch die Process.WaitForExit- Methode in der untergeordneten Methode verwenden, um benachrichtigt zu werden, wenn der übergeordnete process beendet wird, aber im Falle des Task-Managers möglicherweise nicht.

Es gibt eine weitere relevante Methode, die einfach und effektiv ist, um die Kinderprozesse beim Beenden des Programms zu beenden. Sie können einen Debugger von der übergeordneten class implementieren und anfügen . Wenn der Elternprozess endet, werden untergeordnete processe vom Betriebssystem beendet. Es kann auf beide Arten einen Debugger an das übergeordnete Element vom untergeordneten Element anhängen (beachten Sie, dass Sie jeweils nur einen Debugger anfügen können). Mehr Informationen zum Thema finden Sie hier .

Hier haben Sie eine Dienstprogrammklasse, die einen neuen process startet und einen Debugger anfügt. Es wurde von diesem Beitrag von Roger Knapp adaptiert. Die einzige Voraussetzung ist, dass beide processe die gleiche Bitness teilen müssen. Sie können einen 32-Bit-process nicht aus einem 64-Bit-process heraus debuggen oder umgekehrt.

 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("coreel32.dll", SetLastError = true)] private static extern bool DebugActiveProcess(int dwProcessId); [DllImport("coreel32.dll", SetLastError = true)] private static extern bool WaitForDebugEvent([Out] out DEBUG_EVENT lpDebugEvent, int dwMilliseconds); [DllImport("coreel32.dll", SetLastError = true)] private static extern bool ContinueDebugEvent(int dwProcessId, int dwThreadId, int dwContinueStatus); [DllImport("coreel32.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? } } } 

Verwendung:

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

Ich suchte nach einer Lösung für dieses Problem, die keinen unmanaged Code erforderte. Ich war auch nicht in der Lage, die standardmäßige Eingabe / Ausgabe-Umleitung zu verwenden, da es sich um eine Windows Forms-Anwendung handelte.

Meine Lösung bestand darin, im übergeordneten process eine benannte Pipe zu erstellen und anschließend den untergeordneten process mit derselben Pipe zu verbinden. Wenn der Elternprozess beendet wird, wird die Pipe unterbrochen und das Kind kann dies erkennen.

Im Folgenden finden Sie ein Beispiel mit zwei Konsolenanwendungen:

Elternteil

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

Kind

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

Verwenden Sie Event-Handler , um Hooks für einige Exit-Szenarien zu erstellen:

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

Ich sehe zwei Möglichkeiten:

  1. Wenn Sie genau wissen, welcher Child-process gestartet werden könnte, und Sie sicher sind, dass er nur von Ihrem Hauptprozess aus gestartet wird, können Sie einfach nach dem Namen suchen und sie löschen.
  2. Iteriere durch alle processe und erledige jeden process, der deinen process als Elternteil hat (ich denke, du musst die Kindprozesse zuerst beenden). Hier wird erklärt, wie Sie die Elternprozess-ID erhalten können.

Ich habe eine untergeordnete processverwaltungsbibliothek erstellt, in der der übergeordnete process und der untergeordnete process anhand einer bidirektionalen WCF-Pipe überwacht werden. Wenn entweder der Kindprozess beendet wird oder der Elternprozess sich gegenseitig beendet, wird dies gemeldet. Es ist auch ein Debugger-Helfer verfügbar, der den VS-Debugger automatisch an den gestarteten Kindprozess anhängt

Projektseite:

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

NuGet-Pakete:

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

Rufen Sie job.AddProcess nach dem Start des processes besser auf:

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

Beim Aufrufen von AddProcess vor dem Beenden werden untergeordnete processe nicht gelöscht. (Windows 7 SP1)

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