ฆ่ากระบวนการเด็กเมื่อกระบวนการผู้ปกครองถูกฆ่าตาย


156

ฉันกำลังสร้างกระบวนการใหม่โดยใช้System.Diagnostics.Processคลาสจากแอปพลิเคชันของฉัน

ฉันต้องการให้กระบวนการนี้ถูกฆ่าเมื่อ / หากแอปพลิเคชันของฉันล้มเหลว แต่ถ้าฉันฆ่าแอปพลิเคชันของฉันจาก Task Manager กระบวนการลูกจะไม่ถูกฆ่า

มีวิธีใดบ้างที่จะทำให้กระบวนการลูกต้องพึ่งพากระบวนการพ่อแม่?

คำตอบ:


176

จากฟอรัมนี้ให้เครดิตกับ 'Josh'

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;

กุญแจสำคัญที่นี่คือการตั้งค่าวัตถุงานอย่างถูกต้อง ใน Constructor ให้ฉันตั้งค่า "ขีด จำกัด" เพื่อ 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);

4
ฉันจะเพิ่มลิงก์ไปยังCloseHandle
Austin Salonen

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
ตกลง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.

@FrankQ เป็นสิ่งสำคัญที่จะต้องให้ Windows ปิด s_jobHandle ให้กับเราเมื่อกระบวนการของเราสิ้นสุดลงเนื่องจากกระบวนการของเราอาจสิ้นสุดลงโดยไม่คาดคิด (เช่นจากการหยุดทำงานหรือหากผู้ใช้ใช้ตัวจัดการงาน) ดูความคิดเห็นของฉันเกี่ยวกับ s_jobHandle
รอน

มันใช้งานได้สำหรับฉัน ฉันขอความเห็นของคุณเกี่ยวกับการใช้ธง JOB_OBJECT_LIMIT_BREAKAWAY_OK ได้ไหม docs.microsoft.com/en-us/windows/desktop/api/winnt/…
Yiping

1
@yiping ดูเหมือน CREATE_BREAKAWAY_FROM_JOB อนุญาตให้กระบวนการลูกของคุณวางไข่กระบวนการที่สามารถอยู่ได้นานกว่ากระบวนการเดิมของคุณ นั่นเป็นข้อกำหนดที่แตกต่างจากที่ OP ต้องการ แต่ถ้าคุณสามารถทำให้กระบวนการดั้งเดิมของคุณวางไข่กระบวนการที่ยาวนาน (และไม่ใช้ ChildProcessTracker) ที่จะง่ายขึ้น
รอน

@Ron คุณสามารถเพิ่มการโอเวอร์โหลดที่ยอมรับเพียงแค่การจัดการกระบวนการและไม่ใช่กระบวนการทั้งหมดหรือไม่?
Jannik

47

โพสต์นี้มีจุดประสงค์เพื่อเป็นส่วนขยายของคำตอบ @Matt Howells โดยเฉพาะสำหรับผู้ที่ประสบปัญหาในการใช้งาน Job Objects ภายใต้Vista หรือ Win7โดยเฉพาะอย่างยิ่งถ้าคุณได้รับข้อผิดพลาดถูกปฏิเสธการเข้าถึง ('5') เมื่อเรียก AssignProcessToJobObject

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 ด้วย

คำอธิบายแบบเต็ม

การเชื่อมโยงงานของคุณจะล้มเหลวด้วยข้อผิดพลาดถูกปฏิเสธการเข้าถึงหากกระบวนการที่คุณเริ่มมีการเชื่อมโยงกับงานอื่นอยู่แล้ว ป้อน Program Compatibility Assistant ซึ่งเริ่มต้นใน Windows Vista จะกำหนดกระบวนการทุกชนิดให้กับงานของตนเอง

ใน Vista คุณสามารถทำเครื่องหมายแอปพลิเคชันของคุณเพื่อแยกออกจาก PCA โดยเพียงรวมถึงรายการแอปพลิเคชัน Visual Studio ดูเหมือนจะทำสิ่งนี้กับแอพ. NET โดยอัตโนมัติดังนั้นคุณก็เรียบร้อย

รายการง่าย ๆ ไม่ตัดใน Win7 อีกต่อไป [1] ที่นั่นคุณต้องระบุว่าคุณเข้ากันได้กับ Win7 ด้วยแท็กในรายการของคุณโดยเฉพาะ [2]

สิ่งนี้ทำให้ฉันกังวลเกี่ยวกับ Windows 8 ฉันจะต้องเปลี่ยนรายการอีกครั้งหรือไม่ เห็นได้ชัดว่ามีการหยุดพักบนคลาวด์เนื่องจาก Windows 8 ตอนนี้อนุญาตให้กระบวนการเป็นของหลายงาน [3] ดังนั้นฉันยังไม่ได้ทดสอบ แต่ฉันจินตนาการว่าความบ้าคลั่งนี้จะสิ้นสุดลงในตอนนี้ถ้าคุณเพียงแค่รวมรายการกับข้อมูลที่ได้รับการสนับสนุน

เคล็ดลับที่ 1 : ถ้าคุณพัฒนาแอพ. NET ด้วย Visual Studio เหมือนเดิมที่นี่ [4] เป็นคำแนะนำที่ดีเกี่ยวกับวิธีปรับแต่งแอปพลิเคชันของคุณเอง

เคล็ดลับที่ 2 : ระมัดระวังการเปิดแอปพลิเคชันของคุณจาก Visual Studio ฉันพบว่าหลังจากเพิ่มรายการที่เหมาะสมแล้วฉันยังคงมีปัญหากับ PCA เมื่อเปิดใช้งานจาก Visual Studio แม้ว่าฉันจะใช้ Start โดยไม่มีการดีบักก็ตาม อย่างไรก็ตามการเปิดแอปพลิเคชันของฉันจาก Explorer ทำงานได้ หลังจากเพิ่ม devenv ด้วยตนเองสำหรับการแยกจาก PCA โดยใช้รีจิสตรีการเริ่มต้นแอปพลิเคชันที่ใช้ Job Objects จาก VS ก็เริ่มทำงานเช่นกัน [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 เพราะเรา-the-กฎ-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

จากนั้นในกระบวนการลูกใช้ประโยชน์จากข้อเท็จจริงที่ว่าReads จากสตรีมอินพุตมาตรฐานจะกลับมาด้วยอย่างน้อย 1 ไบต์จนกว่ากระแสจะปิดเมื่อพวกเขาจะเริ่มกลับ 0 ไบต์ โครงร่างของวิธีที่ฉันลงเอยด้วยการทำสิ่งนี้อยู่ด้านล่าง วิธีของฉันยังใช้ปั๊มข้อความเพื่อให้เธรดหลักพร้อมใช้งานสำหรับสิ่งอื่นนอกเหนือจากการดูมาตรฐาน แต่วิธีการทั่วไปนี้สามารถใช้ได้โดยไม่มีปั๊มข้อความ

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

Caveats กับวิธีการนี้:

  1. child .exe ที่แท้จริงที่เปิดตัวต้องเป็นแอปพลิเคชั่นคอนโซลดังนั้นจึงยังคงติดอยู่กับ stdin / out / err ในตัวอย่างข้างต้นผมได้อย่างง่ายดายดัดแปลงโปรแกรมที่มีอยู่ของฉันที่ใช้ปั๊มข้อความ ( แต่ไม่ได้แสดง GUI) โดยเพียงแค่การสร้างโครงการคอนโซลเล็ก ๆ ที่อ้างถึงโครงการที่มีอยู่ instantiating บริบทใบสมัครของฉันและเรียกApplication.Run()ภายในMainวิธีการ คอนโซล. exe

  2. ในทางเทคนิคนี่เป็นเพียงสัญญาณของกระบวนการเด็กเมื่อผู้ปกครองออกจากดังนั้นมันจะทำงานว่ากระบวนการผู้ปกครองออกจากปกติหรือผิดพลาด แต่ยังขึ้นอยู่กับกระบวนการเด็กเพื่อดำเนินการปิดตัวเอง นี่อาจเป็นหรือไม่ใช่สิ่งที่คุณต้องการ ...


11

วิธีหนึ่งคือการส่ง PID ของกระบวนการหลักไปยังเด็ก เด็กจะสำรวจความคิดเห็นเป็นระยะหากกระบวนการที่มี pid ที่ระบุมีอยู่หรือไม่ ถ้าไม่มันก็จะเลิก

คุณยังสามารถใช้วิธีการProcess.WaitForExitในเมธอด child เพื่อรับการแจ้งเตือนเมื่อกระบวนการพาเรนต์สิ้นสุดลง แต่อาจไม่ทำงานในกรณีของ Task Manager


ฉันจะส่ง PID ของผู้ปกครองไปที่เด็กได้อย่างไร มีวิธีแก้ปัญหาระบบหรือไม่? ฉันไม่สามารถแก้ไขไบนารีกระบวนการลูกได้
SiberianGuy

1
ถ้าคุณไม่สามารถปรับเปลี่ยนกระบวนการลูกคุณไม่สามารถใช้โซลูชันของฉันได้แม้ว่าคุณจะส่ง PID ไป
Giorgi

@Idsa คุณสามารถส่งผ่านทางบรรทัดคำสั่ง:Process.Start(string fileName, string arguments)
Distortum

2
แทนที่จะสำรวจความคิดเห็นคุณสามารถขอเข้าร่วมในเหตุการณ์ออกในคลาสกระบวนการ
RichardOD

พยายาม แต่กระบวนการผู้ปกครองไม่ออกจากเสมอหากยังมีชีวิตอยู่ (อย่างน้อยในกรณีของฉัน Cobol-> NET) ง่ายต่อการตรวจสอบการดูลำดับชั้นของกระบวนการใน Sysinternals ProcessExplorer
Ivan Ferrer Villa

8

มีวิธีการอื่นที่เกี่ยวข้องง่ายและมีประสิทธิภาพในการทำให้กระบวนการเด็กเสร็จสิ้นเมื่อสิ้นสุดโปรแกรม คุณสามารถใช้และแนบดีบักเกอร์จากพาเรนต์ เมื่อกระบวนการหลักสิ้นสุดลงกระบวนการลูกจะถูกฆ่าโดยระบบปฏิบัติการ สามารถไปได้ทั้งสองวิธีในการแนบดีบักเกอร์กับพาเรนต์จากชายด์ (โปรดทราบว่าคุณสามารถแนบดีบักเกอร์ได้ครั้งละหนึ่ง) คุณสามารถหาข้อมูลเพิ่มเติมเกี่ยวกับเรื่องที่นี่ที่นี่

ที่นี่คุณมีคลาสยูทิลิตี้ที่เปิดตัวกระบวนการใหม่และแนบดีบักเกอร์ มันได้รับการดัดแปลงจากกระทู้นี้โดย Roger Knapp ข้อกำหนดเพียงอย่างเดียวคือกระบวนการทั้งสองจำเป็นต้องแบ่งปันพยานหลักฐานเดียวกัน คุณไม่สามารถดีบักกระบวนการ 32 บิตจากกระบวนการ 64 บิตหรือในทางกลับกัน

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

ใช้ตัวจัดการเหตุการณ์เพื่อสร้าง hooks ในสถานการณ์การออกบางอย่าง:

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. วนซ้ำทุกกระบวนการและฆ่าทุกกระบวนการที่มีกระบวนการเป็นผู้ปกครอง (ฉันเดาว่าคุณต้องฆ่ากระบวนการลูกก่อน) นี่คือคำอธิบายวิธีที่คุณจะได้รับรหัสกระบวนการหลัก

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

โทรหางานเพิ่มกระบวนการดีกว่าที่จะทำหลังจากเริ่มกระบวนการ:

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

นอกจากนี้ยังมีความอุดมสมบูรณ์ของการแก้ปัญหาอีกเสนอ ....

ปัญหาที่เกิดขึ้นกับพวกเขาหลายคนก็คือพวกเขาต้องพึ่งพากระบวนการพ่อแม่และลูกเพื่อปิดระบบอย่างเป็นระเบียบซึ่งไม่เป็นความจริงเสมอไปเมื่อการพัฒนากำลังดำเนินอยู่ ฉันพบว่ากระบวนการลูกของฉันมักจะถูกกำพร้าเมื่อใดก็ตามที่ฉันยุติกระบวนการหลักในการดีบักเกอร์ซึ่งทำให้ฉันต้องฆ่ากระบวนการกำพร้า (es) กับ Task Manager เพื่อสร้างโซลูชันของฉันใหม่

วิธีการแก้ปัญหา: ผ่าน 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.