Kill child process when parent process is killed

Tworzę nowe procesy za pomocą klasy System.Diagnostics.Process z mojej aplikacji.

chcę, aby te procesy zostały zabite, gdy / jeśli moja aplikacja uległa awarii. Ale jeśli zabiję moją aplikację z Menedżera zadań, procesy potomne nie zostaną zabite.

Czy istnieje sposób na uzależnienie procesów potomnych od procesu rodzica?

Author: M Katz, 2010-07-27

16 answers

From this forum , credit to 'Josh'.

Application.Quit() i Process.Kill() są możliwymi rozwiązaniami, ale okazały się zawodne. Gdy główna aplikacja umrze, nadal będziesz mieć uruchomione procesy potomne. Tak naprawdę chcemy, aby procesy potomne umierały, gdy tylko umrze główny proces.

Rozwiązaniem jest użycie "job objects" http://msdn.microsoft.com/en-us/library/ms682409 (VS. 85). aspx .

Chodzi o to, aby stworzyć "obiekt zadania" dla Twojego głównego aplikacji i zarejestrować procesy potomne w obiekcie zadania. Jeśli główny proces umrze, system operacyjny zajmie się zakończeniem procesów potomnych.

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

}

Patrząc na konstruktora ...

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

Kluczem jest prawidłowe ustawienie obiektu zadania. W konstruktorze ustawiam "limity" na 0x2000, co jest wartością liczbową dla JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE.

MSDN definiuje tę flagę jako:

Powoduje wszystkie procesy związane z zadanie, aby zakończyć, gdy ostatni uchwyt do pracy jest zamknięty.

Gdy ta klasa zostanie ustawiona...musisz tylko zarejestrować każdy proces dziecka w pracy. Na przykład:

[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);
 179
Author: Matt Howells,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-11-04 11:37:31

Ta odpowiedź zaczęła się od @Matt Howells' excellent answer plus Inne (patrz linki w kodzie poniżej). Ulepszenia:

  • obsługuje 32-bitowe i 64-bitowe.
  • rozwiązuje pewne problemy w odpowiedzi @ Matt Howells:
    1. mały wyciek pamięci extendedInfoPtr
    2. błąd kompilacji 'Win32' i
    3. wyjątek niezrównoważony stosem otrzymałem w wywołaniu CreateJobObject (używając Windows 10, Visual Studio 2015, 32-bit).
  • Nazwa zadania, więc jeśli używasz SysInternals, na przykład, można łatwo go znaleźć.
  • ma nieco prostsze API i mniej kodu.

Oto jak użyć tego kodu:

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

Do obsługi Windows 7 wymaga:

W moim przypadku nie musiałem obsługiwać Windows 7, więc mam proste sprawdzenie w top of the static konstruktor poniżej.

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

Starannie przetestowałem zarówno 32-bitowe, jak i 64-bitowe wersje struktur, programowo porównując ze sobą wersje zarządzane i natywne (ogólny rozmiar, jak również offsety dla każdego elementu).

Testowałem ten kod na Windows 7, 8 i 10.
 58
Author: Ron,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-09 15:58:51

Ten post jest przeznaczony jako rozszerzenie odpowiedzi @ Matt Howells, specjalnie dla tych, którzy napotykają problemy z używaniem obiektów zadań w Vista lub Win7 , szczególnie jeśli podczas wywoływania Assignprocesstojobject otrzymasz błąd odmowy dostępu ('5').

Tl; dr

Aby zapewnić kompatybilność z Vista i Win7, Dodaj następujący manifest do procesu macierzystego. 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>

Zauważ, że po dodaniu nowego manifestu w Visual Studio 2012 będzie on zawierał powyższy fragment już więc nie trzeba go kopiować z hear. Będzie również zawierać węzeł Dla Windows 8.

Pełne wyjaśnienie

Twoje skojarzenie z pracą nie powiedzie się z błędem Odmowa dostępu, jeśli proces, który rozpoczynasz, jest już powiązany z innym zadaniem. Wpisz Program Compatibility Assistant, który począwszy od systemu Windows Vista przypisze wszystkie rodzaje procesów do własnych zadań.

W Vista można oznaczyć wniosek o wykluczenie z PCA po prostu w tym manifest aplikacji. Wydaje się, że Visual Studio robi to automatycznie dla aplikacji. NET, więc wszystko w porządku.

Prosty manifest nie tnie go już w Win7. [1] tam musisz dokładnie określić, że jesteś kompatybilny z Win7 za pomocą tagu w manifeście. [2]

To doprowadziło mnie do martwienia się o Windows 8. Czy będę musiał zmienić mój manifest jeszcze raz? Najwyraźniej jest przerwa w chmurach, ponieważ Windows 8 pozwala teraz procesowi należeć do wielu zadań. [3] So I jeszcze tego nie testowałem, ale wyobrażam sobie, że to szaleństwo się skończy, jeśli po prostu dołączysz manifest z informacją.

Wskazówka 1: Jeśli tworzysz aplikację. NET z Visual Studio, tak jak ja, tutaj [4] są ładne instrukcje, jak dostosować manifest aplikacji.

Wskazówka 2 : Uważaj na uruchamianie aplikacji z Visual Studio. Stwierdziłem, że po dodaniu odpowiedniego manifestu nadal miałem problemy z PCA, gdy uruchamianie z Visual Studio, nawet jeśli używałem Start bez debugowania. Uruchomienie aplikacji z Explorera zadziałało. Po ręcznym dodaniu devenv do wykluczenia z PCA przy użyciu rejestru, uruchamianie aplikacji, które używały obiektów zadania z VS również zaczęło działać. [5]

Wskazówka 3: Jeśli chcesz wiedzieć, czy PCA jest Twoim problemem, spróbuj uruchomić aplikację z wiersza poleceń lub skopiuj program na dysk sieciowy i uruchom go stamtąd. PCA jest automatycznie wyłączone w tych kontekstach.

[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-because-we-changed-the-rules-on-you.aspx

[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 : "Proces może być powiązany z więcej niż jednym zadaniem w systemie Windows 8"

[4] Jak mogę osadzić manifest aplikacji w aplikacji za pomocą VS2008?

[5] Jak zatrzymać debugger programu Visual Studio rozpoczynający mój proces w obiekcie zadania?

 47
Author: adam smith,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:26:34

Oto alternatywa, która może działać dla niektórych, gdy masz kontrolę nad kodem, który uruchamia proces potomny. Zaletą tego podejścia jest to, że nie wymaga żadnych natywnych wywołań systemu Windows.

Podstawową ideą jest przekierowanie standardowego wejścia dziecka do strumienia, którego drugi koniec jest podłączony do rodzica, i użycie tego strumienia do wykrywania, kiedy rodzic odszedł. Kiedy używasz System.Diagnostics.Process do uruchomienia potomka, łatwo jest upewnić się, że jego standardowe wejście jest przekierowane:

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

I wtedy, na proces potomny, wykorzystaj fakt, że Read S ze standardowego strumienia wejściowego zawsze powróci z co najmniej 1 bajtem, dopóki strumień nie zostanie zamknięty, kiedy zaczną zwracać 0 bajtów. Zarys sposobu, w jaki skończyłem to robić jest poniżej; mój sposób używa również pompy wiadomości, aby utrzymać główny wątek Dostępny dla rzeczy innych niż oglądanie standard W, ale to ogólne podejście może być używane bez pompy wiadomości zbyt.

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

Zastrzeżenia do takiego podejścia:

  1. Prawdziwe dziecko .uruchamiany exe musi być aplikacją konsolową, więc pozostaje dołączony do stdin / out / err. Podobnie jak w powyższym przykładzie, łatwo zaadaptowałem moją istniejącą aplikację, która używała pompy komunikatów (ale nie pokazywała GUI), po prostu tworząc mały projekt konsoli, który odwołuje się do istniejącego projektu, tworząc instancję mojego kontekstu aplikacji i wywołując Application.Run() wewnątrz metody Main konsoli .exe.

  2. Technicznie, to tylko sygnały proces potomny po wyjściu rodzica, więc będzie działać niezależnie od tego, czy proces macierzysty zakończył normalnie, czy rozbił się, ale nadal do procesów potomnych, aby wykonać własne zamknięcie. To może być to, czego chcesz...

 16
Author: mbaynton,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-11-28 21:46:48

Jednym ze sposobów jest przekazanie PID procesu rodzica dziecku. Dziecko będzie okresowo sprawdzać, czy proces o podanym pid istnieje, czy nie. Jeśli nie, to po prostu odejdzie.

Można również użyć Proces.WaitForExit metoda w metodzie potomnej, która ma być powiadamiana o zakończeniu procesu nadrzędnego, ale może nie działać w przypadku Menedżera zadań.

 12
Author: Giorgi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-07-27 11:09:28

Istnieje inna istotna metoda, łatwa i skuteczna, aby zakończyć procesy potomne po zakończeniu programu. Możesz zaimplementować i dołączyć do nich debugger od rodzica; gdy proces rodzica zakończy się, procesy potomne zostaną zabite przez system operacyjny. Może to iść w obie strony dołączanie debuggera do rodzica z dziecka (zauważ, że możesz dołączyć tylko jeden debugger na raz). Więcej informacji na temat znajdziesz tutaj .

Tutaj masz klasę użyteczności, która uruchamia nowy przetwarza i dołącza do niego debugger. Został on zaadaptowany z tego postu przez Rogera Knappa. Jedynym wymogiem jest to, że oba procesy muszą mieć tę samą bitowość. Nie można debugować procesu 32-bitowego z procesu 64-bitowego lub odwrotnie.

public class ProcessRunner
{
    // see http://csharptest.net/1051/managed-anti-debugging-how-to-prevent-users-from-attaching-a-debugger/
    // see https://stackoverflow.com/a/24012744/2982757

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

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

Użycie:

    new ProcessRunner().StartProcess("c:\\Windows\\system32\\calc.exe");
 11
Author: Marco Regueira,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-12-04 15:00:54

Szukałem rozwiązania tego problemu, które nie wymagało kodu niezarządzanego. Nie mogłem również użyć standardowego przekierowania wejścia/wyjścia, ponieważ była to aplikacja Windows Forms.

Moim rozwiązaniem było stworzenie nazwanego potoku w procesie nadrzędnym, a następnie podłączenie procesu podrzędnego do tego samego potoku. Jeśli proces macierzysty zakończy się, rura zostanie przerwana i dziecko może to wykryć.

Poniżej przykład użycia dwóch konsol zastosowania:

Rodzic

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

Dziecko

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);
}
 9
Author: Alsty,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-08 09:40:45

Użyj procedury obsługi zdarzeń do tworzenia hooków na kilku scenariuszach wyjścia:

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(); };
 4
Author: Justin Harris,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-24 17:24:50

Tylko moja wersja z 2018 roku. Użyj jej na boku swojej metody 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);
    }
 2
Author: Lenor,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-10-28 13:39:44

Widzę dwie opcje:

  1. Jeśli wiesz dokładnie, jaki proces potomny może zostać uruchomiony i jesteś pewien, że został uruchomiony tylko z twojego głównego procesu, możesz rozważyć po prostu wyszukanie ich po nazwie i zabicie ich.
  2. Iteruj wszystkie procesy i zabij wszystkie procesy, które mają twój proces jako rodzica (chyba musisz najpierw zabić procesy potomne). tutaj {[7] } jest wyjaśnione, w jaki sposób można uzyskać identyfikator procesu nadrzędnego.
 1
Author: Stefan Egli,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:34:53

Stworzyłem bibliotekę zarządzania procesami potomnymi, w której proces nadrzędny i proces potomny są monitorowane za pomocą dwukierunkowej rury WCF. Jeśli proces potomny zakończy się lub proces nadrzędny zakończy się wzajemnie jest powiadamiany. Dostępny jest również debugger helper, który automatycznie dołącza debugger VS do rozpoczętego procesu potomnego

Strona projektu:

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

NuGet Pakiety:

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

 1
Author: Thomas Maierhofer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-08 23:15:22

Kolejny dodatek do bogactwa dotychczas proponowanych rozwiązań....

Problem z wieloma z nich polega na tym, że polegają na tym, że proces rodzica i dziecka zostanie zamknięty w uporządkowany sposób, co nie zawsze jest prawdą, gdy rozwój jest w toku. Odkryłem, że mój proces potomny był często osierocany, gdy zakończyłem proces macierzysty w debuggerze, co wymagało ode mnie zabicia osieroconych procesów za pomocą Menedżera zadań w celu odbudowania mojego rozwiązania.

The rozwiązanie: przekazanie ID procesu nadrzędnego w wierszu poleceń (lub nawet mniej inwazyjne, w zmiennych środowiskowych) procesu potomnego.

W procesie nadrzędnym identyfikator procesu jest dostępny jako:

Process.CurrentProcess.Id;

W procesie potomnym:

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();
};
Mam dwa umysły co do tego, czy jest to akceptowalna praktyka w kodzie produkcyjnym. Z jednej strony, to nigdy nie powinno się zdarzyć. Ale z drugiej strony, może to oznaczać różnicę między ponownym uruchomieniem procesu, a ponownym uruchomieniem serwera produkcyjnego. Oraz to, co nigdy nie powinno się zdarzyć, często się zdarza.

I na pewno jest przydatny podczas debugowania uporządkowanych problemów z zamykaniem.

 1
Author: Robin Davies,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-07-25 23:17:33

Zadzwoń do Joba.AddProcess lepiej zrobić po rozpoczęciu procesu:

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

Podczas wywoływania AddProcess przed zakończeniem, procesy potomne nie są zabijane. (Windows 7 SP1)

private void KillProcess(Process proc)
{
    var job = new Job();
    job.AddProcess(proc.Handle);
    job.Close();
}
 0
Author: Alexey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-07 11:52:38

Rozwiązanie, które zadziałało dla mnie:

Podczas tworzenia procesu Dodaj tag process.EnableRaisingEvents = true;:

csc = new Process();
csc.StartInfo.UseShellExecute = false;
csc.StartInfo.CreateNoWindow = true;
csc.StartInfo.FileName = Path.Combine(HLib.path_dataFolder, "csc.exe");
csc.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
csc.StartInfo.ErrorDialog = false;
csc.StartInfo.RedirectStandardInput = true;
csc.StartInfo.RedirectStandardOutput = true;
csc.EnableRaisingEvents = true;
csc.Start();
 0
Author: Whitebrim,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-07-26 07:19:34

Miałem ten sam problem. Tworzyłem procesy potomne, które nigdy nie zginęły, jeśli moja główna aplikacja uległa awarii. Musiałem zniszczyć ręcznie procesy potomne podczas debugowania. Okazało się, że nie ma potrzeby, aby dzieci w pewnym stopniu zależały od rodzica. W moim głównym, dodałem spróbować złapać zrobić CleanUp () procesów potomnych na wyjściu.

    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        try
        {
            Application.Run(new frmMonitorSensors());
        }
        catch(Exception ex)
        {
            CleanUp();
            ErrorLogging.Add(ex.ToString());
        }
    }

    static private void CleanUp()
    {
        List<string> processesToKill = new List<string>() { "Process1", "Process2" };
        foreach (string toKill in processesToKill)
        {
            Process[] processes = Process.GetProcessesByName(toKill);
            foreach (Process p in processes)
            {
                p.Kill();
            }
        }
    }
 0
Author: Daniel Silverwood,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-08-26 00:53:12

Jeśli możesz złapać sytuację, kiedy twoje drzewo procesów powinno zostać zabite, od. NET 5.0 (. NET Core 3.0) możesz użyć Proces.Kill (bool entreprocesstree)

 0
Author: user1234567,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2021-01-14 10:24:38