父进程被杀死时终止子进程

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (2)
  • 关注 (0)
  • 查看 (41)

我正在创建新的进程System.Diagnostics.Process从我的申请中提取。

我希望当/如果我的应用程序崩溃时,这个进程被杀死。但是,如果我从任务管理器中杀死我的应用程序,子进程就不会被杀死。

是否有任何方法使子进程依赖父进程?

提问于
用户回答回答于

Application.Quit()Process.Kill()是可能的解决方案,但已经被证明是不可靠的。当主应用程序死亡时,仍然会有子进程在运行。我们真正想要的是,当主进程死掉时,子进程就会死掉。

这样做的目的是为主应用程序创建一个“作业对象”,并向作业对象注册子进程。如果主进程死亡,操作系统将负责终止子进程。

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;

这里的关键是正确地设置作业对象。在构造函数中,我将“限制”设置为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);
用户回答回答于

这篇文章是作为@Matt Howells答案的扩展,专门针对那些在使用JobObjects时遇到问题的Vista或Win 7,特别是当在调用AssignProcessToJobObject时得到一个拒绝访问的错误(‘5’)。

TL;DR

为了确保与Vista和Win 7的兼容性,向.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>

请注意,在VisualStudio 2012中添加新清单时,它将包含上述片段,因此不需要从SEAR中复制它。它还将包括一个用于Windows 8的节点。

充分解释

如果正在启动的进程已经与其他作业关联,则作业关联将失败,从而导致访问拒绝错误。输入程序兼容性助理,从WindowsVista开始,它将为自己的作业分配所有类型的进程。

在Vista中,可以通过简单地包含应用程序清单来标记应用程序被排除在PCA之外。VisualStudio似乎会自动为.NET应用程序执行此操作,因此您在这里很好。

在Win 7中,一个简单的清单不再切割它。

一在这里,必须明确指定您与Win 7兼容清单中的标记。

二这让我担心Windows 8。我还要再换一次舱单吗?显然,云端有了突破,因为Windows 8现在允许进程属于多个作业。

三因此,我还没有测试它,但我想,如果您只包含带有支持OS信息的清单,这种疯狂就会结束。

尖端1:如果正在用VisualStudio开发.NET应用程序,就像我以前一样四是关于如何自定义应用程序清单的一些很好的说明。

尖端2::在从VisualStudio启动应用程序时要小心。我发现,在添加了适当的清单之后,我在从VisualStudio启动时仍然遇到PCA问题,即使我使用Start而不进行调试。不过,从Explorer启动应用程序是有效的。在使用注册表手动添加devenv以排除PCA之外之后,启动使用VS中的作业对象的应用程序也开始工作。五

尖端3:如果想知道PCA是否是你的问题,请尝试从命令行启动应用程序,或者将程序复制到网络驱动器并从那里运行。PCA在这些上下文中被自动禁用。

扫码关注云+社区