부모 프로세스가 종료 될 때 자식 프로세스 종료


156

System.Diagnostics.Process내 응용 프로그램의 클래스를 사용하여 새 프로세스를 만들고 있습니다.

내 응용 프로그램이 충돌했을 때 /있는 경우이 프로세스를 종료하고 싶습니다. 그러나 작업 관리자에서 응용 프로그램을 종료하면 자식 프로세스가 종료되지 않습니다.

자식 프로세스를 부모 프로세스에 종속시키는 방법이 있습니까?

답변:


176

에서 이 포럼 '조쉬'로, 신용.

Application.Quit()Process.Kill()가능한 해결 방법은 다음과 같습니다,하지만 신뢰성이 입증되었다. 기본 응용 프로그램이 종료 되더라도 자식 프로세스가 계속 실행됩니다. 우리가 정말로 원하는 것은 주 프로세스가 죽 자마자 자식 프로세스가 죽는 것입니다.

해결책은 "작업 개체"를 사용하는 것입니다. http://msdn.microsoft.com/en-us/library/ms682409(VS.85).aspx 입니다.

기본 응용 프로그램에 대한 "작업 개체"를 만들고 자식 프로세스를 작업 개체에 등록하는 것이 좋습니다. 기본 프로세스가 종료되면 OS는 하위 프로세스를 종료합니다.

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

}

생성자를 보면 ...

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

여기서 핵심은 작업 개체를 올바르게 설정하는 것입니다. 생성자에서 "limits"를 0x2000으로 설정합니다.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE . .

MSDN은이 플래그를 다음과 같이 정의합니다.

작업에 대한 마지막 핸들이 닫힐 때 작업과 연관된 모든 프로세스가 종료되도록합니다.

이 클래스가 설정되면 각 하위 프로세스를 작업에 등록하면됩니다. 예를 들면 다음과 같습니다.

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


6
불행히도 64 비트 모드에서이를 실행할 수 없었습니다. 여기 에 이것을 기반으로 한 실제 예를 게시했습니다.
Alexander Yezutov

2
@Matt Howells-어디 Win32.CloseHandle에서 유래합니까? 이것은 kernel32.dll에서 가져 옵니까? 일치하는 서명이 있지만 다른 API 함수처럼 명시 적으로 가져 오지 않습니다.
밀교 화면 이름

6
64 비트 모드 앱의 경우-> stackoverflow.com/a/5976162 Vista / Win7의 경우 문제-> social.msdn.microsoft.com/forums/en-US/windowssecurity/thread/…
hB0

3
Ok, MSDN의 말 : JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE 플래그는 JOBOBJECT_EXTENDED_LIMIT_INFORMATION 구조를 사용해야합니다.
SerG

54

이 답변은 @Matt Howells의 탁월한 답변 과 다른 답변으로 시작되었습니다 (아래 코드의 링크 참조). 개량:

  • 32 비트 및 64 비트를 지원합니다.
  • @Matt Howells의 답변에서 일부 문제를 수정합니다.
    1. 작은 메모리 누수 extendedInfoPtr
    2. 'Win32'컴파일 오류
    3. 내가 호출 한 스택 불균형 예외 CreateJobObject(Windows 10, Visual Studio 2015, 32 비트 사용).
  • 예를 들어 SysInternals를 사용하는 경우 작업 이름을 쉽게 찾을 수 있습니다.
  • 다소 간단한 API와 적은 코드가 있습니다.

이 코드를 사용하는 방법은 다음과 같습니다.

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

Windows 7을 지원하려면 다음이 필요합니다.

필자의 경우 Windows 7을 지원할 필요가 없었으므로 아래 정적 생성자의 맨 위에 간단한 확인이 있습니다.

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

필자는 관리 버전과 기본 버전을 서로 프로그래밍 방식으로 비교하여 (전체 크기와 각 멤버의 오프셋) 구조를 통해 32 비트 및 64 비트 버전의 구조체를 신중하게 테스트했습니다.

이 코드는 Windows 7, 8 및 10에서 테스트했습니다.


작업 핸들을 닫는 것은 어떻습니까?
Frank Q.

@ 프랭크 큐. 프로세스가 갑자기 종료되거나 사용자가 작업 관리자를 사용하는 경우와 같이 프로세스가 예기치 않게 종료 될 수 있으므로 프로세스가 종료 될 때 Windows가 s_jobHandle을 닫도록하는 것이 중요합니다. s_jobHandle 's에 대한 내 의견을 참조하십시오.
Ron

나를 위해 일하고 있습니다. JOB_OBJECT_LIMIT_BREAKAWAY_OK 플래그 사용에 대한 귀하의 의견을 물어봐도 될까요? docs.microsoft.com/ko-kr/windows/desktop/api/winnt/…
Yiping

1
@yiping CREATE_BREAKAWAY_FROM_JOB처럼 들리면 자식 프로세스가 원래 프로세스보다 오래 지속될 수있는 프로세스를 생성 할 수 있습니다. OP가 요청한 것과 다른 요구 사항입니다. 대신 원래 프로세스가 오래 지속되는 프로세스를 생성 할 수 있다면 (ChildProcessTracker를 사용하지 않음) 더 간단합니다.
Ron

@Ron 전체 프로세스가 아닌 프로세스 핸들 만 허용하는 오버로드를 추가 할 수 있습니까?
Jannik

47

이 게시물은 @Matt Howells의 답변에 대한 확장으로 , 특히 Vista 또는 Win7 에서 Job Objects 사용에 문제가있는 사람들 , 특히 AssignProcessToJobObject를 호출 할 때 액세스 거부 오류 ( '5')가 발생하는 경우에 적합합니다.

tl; dr

Vista 및 Win7과의 호환성을 유지하려면 .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>

Visual Studio 2012에서 새 매니페스트를 추가하면 위의 스 니펫이 이미 포함되어 있으므로들을 때 복사 할 필요가 없습니다. Windows 8 용 노드도 포함됩니다.

전체 설명

시작중인 프로세스가 이미 다른 작업과 연결되어 있으면 작업 거부가 액세스 거부 오류와 함께 실패합니다. Windows Vista에서 시작하여 모든 종류의 프로세스를 자체 작업에 할당하는 Program Compatibility Assistant를 시작합니다.

Vista에서는 단순히 응용 프로그램 매니페스트를 포함하여 응용 프로그램이 PCA에서 제외되도록 표시 할 수 있습니다. Visual Studio는 .NET 앱에 대해 자동 으로이 작업을 수행하는 것 같습니다.

간단한 매니페스트가 더 이상 Win7에서 잘리지 않습니다. [1] 매니페스트의 태그를 사용하여 Win7과 호환되도록 구체적으로 지정해야합니다. [2]

이로 인해 Windows 8이 걱정되었습니다. 매니페스트를 다시 한 번 변경해야합니까? Windows 8에서 프로세스가 여러 작업에 속할 수 있기 때문에 클라우드에 문제가있는 것 같습니다. [3] 아직 테스트하지는 않았지만 지원되는 OS 정보가 포함 된 매니페스트 만 포함하면이 광기가 끝날 것입니다.

팁 1 : Visual Studio를 사용하여 .NET 앱을 개발하는 경우 여기에서 [4]는 응용 프로그램 매니페스트를 사용자 지정하는 방법에 대한 유용한 지침입니다.

팁 2 : Visual Studio에서 응용 프로그램을 시작할 때주의하십시오. 적절한 매니페스트를 추가 한 후 디버깅하지 않고 시작을 사용해도 Visual Studio에서 시작할 때 PCA에 여전히 문제가 있음을 알았습니다. 그러나 탐색기에서 내 응용 프로그램을 시작하면 효과가 있습니다. 레지스트리를 사용하여 PCA에서 제외 할 수 있도록 devenv를 수동으로 추가 한 후 VS의 Job Objects를 사용한 응용 프로그램 시작도 작동하기 시작했습니다. [5]

팁 3 : PCA가 문제인지 확인하려면 명령 행에서 응용 프로그램을 시작하거나 프로그램을 네트워크 드라이브에 복사 한 후 실행하십시오. 이러한 상황에서는 PCA가 자동으로 비활성화됩니다.

[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-we-change-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 : "프로세스는 Windows 8에서 둘 이상의 작업과 연관 될 수 있습니다"

[4] VS2008을 사용하여 응용 프로그램 매니페스트를 응용 프로그램에 포함시키는 방법은 무엇입니까?

[5] 작업 개체에서 프로세스를 시작하는 Visual Studio 디버거를 중지하는 방법은 무엇입니까?


2
이것들은 주제에 큰 도움이됩니다. 감사합니다! 링크를 포함 하여이 답변의 모든 측면을 활용했습니다.
Johnny Kauffman

16

다음은 자식 프로세스가 실행하는 코드를 제어 할 때 작동 할 수있는 대안입니다. 이 방법의 이점은 기본 Windows 호출이 필요하지 않다는 것입니다.

기본 아이디어는 자녀의 표준 입력을 다른 쪽 끝이 부모에 연결된 스트림으로 리디렉션하고 해당 스트림을 사용하여 부모가 언제 사라 졌는지 감지하는 것입니다. 당신이 사용하는 경우 System.Diagnostics.Process아이를 시작하기 위해서는 표준 입력 리디렉션 할 수 있도록 쉽게 :

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

그런 다음 자식 프로세스 Read에서 표준 입력 스트림의 s는 스트림이 닫힐 때까지 0 바이트를 반환하기 시작할 때 항상 1 바이트 이상으로 반환 된다는 사실을 이용하십시오 . 내가 이것을 끝내는 방식에 대한 개요는 다음과 같습니다. 내 방식은 메시지 펌프를 사용하여 표준 시청 이외의 주요 스레드를 사용할 수 있지만 메시지 펌프 없이이 일반적인 방법을 사용할 수 있습니다.

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

이 접근법에 대한주의 사항 :

  1. 실행되는 실제 자식 .exe는 콘솔 응용 프로그램이어야하므로 stdin / out / err에 연결되어 있습니다. 위의 예에서와 같이 기존 프로젝트를 참조하는 작은 콘솔 프로젝트를 작성하고 애플리케이션 컨텍스트를 인스턴스화 Application.Run()하고 Main메소드의 메소드 내부를 호출하여 메시지 펌프를 사용했지만 GUI를 표시하지 않은 기존 애플리케이션을 쉽게 조정 했습니다. 콘솔 .exe.

  2. 기술적으로 이것은 상위 프로세스가 종료 될 때 하위 프로세스에 신호를 보내기 때문에 상위 프로세스가 정상적으로 종료되었거나 충돌했는지 여부에 관계없이 작동하지만 자체 종료를 수행하기 위해 하위 프로세스까지 계속 작동합니다. 이것은 당신이 원하는 것일 수도 아닐 수도 있습니다 ...


11

한 가지 방법은 부모 프로세스의 PID를 자식에게 전달하는 것입니다. 지정된 pid를 가진 프로세스가 존재하는지 여부에 관계없이 자식은 주기적으로 폴링합니다. 그렇지 않으면 그냥 종료됩니다.

하위 프로세스에서 Process.WaitForExit 메소드를 사용 하여 상위 프로세스가 종료 될 때 알림을받을 수 있지만 작업 관리자의 경우 작동하지 않을 수 있습니다.


부모 PID를 자식에게 어떻게 전달할 수 있습니까? 시스템 솔루션이 있습니까? 자식 프로세스 바이너리를 수정할 수 없습니다.
SiberianGuy

1
자식 프로세스를 수정할 수 없으면 PID를 전달하더라도 내 솔루션을 사용할 수 없습니다.
Giorgi

@Idsa 명령 행을 통해 전달할 수 있습니다.Process.Start(string fileName, string arguments)
Distortum

2
폴링하지 않고 프로세스 클래스에서 Exit 이벤트에 연결할 수 있습니다.
RichardOD

그것을 시도했지만 자식 프로세스가있는 경우 부모 프로세스가 항상 종료되는 것은 아닙니다 (적어도 필자의 경우 Cobol-> NET). Sysinternals ProcessExplorer에서 프로세스 계층 구조를 쉽게 확인할 수 있습니다.
Ivan Ferrer Villa

8

프로그램 종료시 하위 프로세스를 완료하는 쉽고 효과적인 또 다른 관련 방법이 있습니다. 부모로부터 디버거 를 구현하고 연결할 수 있습니다 . 부모 프로세스가 끝나면 OS에 의해 자식 프로세스가 종료됩니다. 자식에서 부모에게 디버거를 연결하는 두 가지 방법을 사용할 수 있습니다 (한 번에 하나의 디버거 만 연결할 수 있음). 주제에 대한 자세한 정보는 여기를 참조하십시오 .

여기에 새로운 프로세스를 시작하고 디버거를 연결하는 유틸리티 클래스가 있습니다. 이 게시물 에서 Roger Knapp에 의해 수정되었습니다 . 유일한 요구 사항은 두 프로세스 모두 동일한 비트를 공유해야한다는 것입니다. 64 비트 프로세스에서 32 비트 프로세스를 디버그하거나 그 반대로 디버그 할 수 없습니다.

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

용법:

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

8

관리되지 않는 코드가 필요없는이 문제에 대한 해결책을 찾고있었습니다. 또한 표준 입력 / 출력 리디렉션은 Windows Forms 응용 프로그램이므로 사용할 수 없었습니다.

내 솔루션은 부모 프로세스에서 명명 된 파이프를 만든 다음 자식 프로세스를 동일한 파이프에 연결하는 것이 었습니다. 부모 프로세스가 종료되면 파이프가 파손되어 자식이이를 감지 할 수 있습니다.

아래는 두 개의 콘솔 응용 프로그램을 사용하는 예입니다.

부모의

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

아이

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

이벤트 핸들러 를 사용 하여 몇 가지 종료 시나리오에 연결하십시오.

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

간단하면서도 효과적입니다.
uncommon_name

2

내 2018 버전입니다. 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
나는 부모 프로세스가 될 때이 실행됩니다 의심 사망 .
springy76

1

두 가지 옵션이 있습니다.

  1. 어떤 하위 프로세스가 시작될 수 있는지 정확히 알고 있고 기본 프로세스에서만 시작할 수 있다고 확신하는 경우 단순히 이름을 기준으로 검색하여 종료 할 수 있습니다.
  2. 모든 프로세스를 반복하고 부모로 프로세스가있는 모든 프로세스를 종료하십시오 (자식 프로세스를 먼저 종료해야한다고 생각합니다). 여기 당신이 부모 프로세스 ID를 얻을 수있는 방법을 설명한다.

1

양방향 WCF 파이프로 인해 부모 프로세스와 자식 프로세스가 모니터링되는 자식 프로세스 관리 라이브러리를 만들었습니다. 하위 프로세스가 종료되거나 상위 프로세스가 서로 종료되면 통지됩니다. VS 디버거를 시작된 자식 프로세스에 자동으로 연결하는 디버거 도우미도 있습니다.

프로젝트 사이트 :

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

NuGet 패키지 :

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


0

프로세스 시작 후 job.AddProcess를 호출하는 것이 좋습니다.

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

종료 전에 AddProcess를 호출하면 자식 프로세스가 종료되지 않습니다. (Windows 7 SP1)

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

프로세스 시작 후 job.AddProcess를 호출하면이 오브젝트 사용 목적이 종료됩니다.
SerG

0

지금까지 제안 된 풍부한 솔루션에 대한 또 다른 추가 사항 ....

그들 중 많은 사람들의 문제는 부모와 자식 프로세스가 순서대로 종료되기 때문에 개발이 진행되는 동안 항상 사실이 아니라는 것입니다. 디버거에서 부모 프로세스를 종료 할 때마다 내 자식 프로세스가 종종 고아라는 것을 알았으므로 솔루션을 다시 빌드하기 위해 작업 관리자로 고아 프로세스를 종료해야했습니다.

해결 방법 : 하위 프로세스의 명령 줄 (또는 환경 변수에서 덜 침습적)에 상위 프로세스 ID를 전달하십시오.

상위 프로세스에서 프로세스 ID는 다음과 같이 사용 가능합니다.

  Process.CurrentProcess.Id;

자식 프로세스에서 :

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

이것이 프로덕션 코드에서 허용 가능한 관행인지 여부에 대해서는 두 가지 생각이 있습니다. 한편으로는 이런 일이 발생하지 않아야합니다. 그러나 다른 한편으로는 프로세스 재시작과 프로덕션 서버 재부팅의 차이를 의미 할 수 있습니다. 그리고 결코 일어나지 않아야 할 일이 종종 있습니다.

또한 시스템 종료 문제를 디버깅 할 때 유용합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.