当父进程被终止时,终止子进程

我正在使用我的应用程序中的 System.Diagnostics.Process类创建新进程。当/如果我的应用程序崩溃时,我希望这个进程被关闭。但是如果从任务管理器中终止应用程序,则不会终止子进程。有没有办法使子进程依赖于父进程?

105276 次浏览

一种方法是将父进程的 PID 传递给子进程。如果具有指定 pid 的进程存在或不存在,则子进程将定期轮询。如果没有,它就会退出。

您还可以在子方法中使用 进程等待退出方法,以便在父进程结束时得到通知,但是在 TaskManager 的情况下可能无法工作。

我看到两个选择:

  1. 如果您确切地知道可以启动哪个子进程,并且确定它们只是从主进程启动的,那么您可以考虑简单地按名称搜索它们并杀死它们。
  2. 遍历所有进程并杀死将您的进程作为父进程的每个进程(我想您需要先杀死子进程)。给你解释了如何获取父进程 id。

这个论坛台,感谢“ Josh”。

Application.Quit()Process.Kill()是可能的解决方案,但已被证明是不可靠的。当您的主应用程序终止时,您仍然可以运行子进程。我们真正想要的是子进程在主进程死亡后立即死亡。

解决方案是使用“作业对象”http://msdn.microsoft.com/en-us/library/ms682409(VS.85).aspx

其思想是为您的主应用程序创建一个“作业对象”,并将您的子进程注册到作业对象中。如果主进程终止,操作系统将负责终止子进程。

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 的回答,特别是对于那些在使用 Vista 还是 Win7下的作业对象时遇到问题的人,尤其是在调用 AssignProcessToJobObject 时遇到访问拒绝错误(’5’)时。

博士

为了确保与 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>

请注意,在 VisualStudio2012中添加新清单时,它将已经包含上述代码段,因此不需要从 hear 中复制它。它还将包括一个用于 Windows8的节点。

完整的解释

如果正在启动的进程已经与另一个作业关联,则作业关联将失败,并出现访问拒绝错误。输入 Program CompatibilityAssistant,从 WindowsVista 开始,它将为自己的作业分配所有类型的进程。

在 Vista 中,您可以通过简单地包含一个应用程序清单来标记您的应用程序被排除在 PCA 之外。VisualStudio 似乎为。NET 应用程序自动,所以你在那里很好。

一个简单的清单不再削减它在 Win7。[1]在这里,您必须特别指定与清单中的标记兼容 Win7。[2]

这让我开始担心 Windows 8。我需要再次更改我的旅客名单吗?显然,云计算已经有了突破,因为 Windows8现在允许一个进程属于多个作业。[3]所以我还没有对它进行测试,但是我想,如果你只是简单地在清单中包含受支持操作系统的信息,这种疯狂的行为就会结束。

技巧1 : 如果您正在开发一个。NET 应用和 Visual Studio 一样,这里[4]有一些关于如何定制应用程序清单的很好的说明。

技巧2 : 小心从 VisualStudio 启动应用程序。我发现,在添加了适当的清单之后,从 VisualStudio 启动 PCA 时仍然存在问题,即使我使用了 Start 而没有调试。然而,从 Explorer 启动我的应用程序是有效的。在使用注册表手动添加 devenv 以排除 PCA 之后,从 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-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: “在 Windows8中,一个进程可以与多个作业关联”

[4] 如何使用 VS2008将应用程序清单嵌入到应用程序中?

[5] 如何停止 VisualStudio 调试器在作业对象中启动进程?

当您控制了子进程运行的代码时,这里有一个替代方案可能对某些人有效。这种方法的好处是它不需要任何本机 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总是返回至少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();
}
}

对这种方法的警告:

  1. 真正的孩子。启动的 exe 必须是一个控制台应用,因此它仍然连接到 stdin/out/err。在上面的例子中,我通过创建一个引用现有项目的小型控制台项目,实例化我的应用程序上下文,并在控制台的 Main方法中调用 Application.Run(),轻松地调整了使用消息泵(但没有显示 GUI)的现有应用程序。前任。

  2. 从技术上讲,这只是 信号的子进程时,父进程退出,所以它将工作,无论父进程退出正常或崩溃,但它仍然对子进程执行自己的关闭。这也许是你想要的,也许不是。

调用 job。在进程开始之后,最好执行以下操作:

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

在终止前调用 AddProcess 时,不会关闭子进程

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

还有另一种相关的方法,简单有效,完成子进程的程序终止。您可以从父进程实现它们,并从父进程实现它们; 当父进程结束时,子进程将被操作系统终止。它可以通过两种方式将调试器从子级附加到父级(请注意,一次只能附加一个调试器)。你可以找到更多关于主题 给你的信息。

这里有一个实用程序类,它启动一个新进程并将调试器附加到该进程。它已改编自 这篇文章的罗杰纳普。唯一的要求是两个进程需要共享相同的位。不能从64位进程调试32位进程,反之亦然。

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
}

用法:

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

我创建了一个子进程管理库,通过一个双向 WCF 管道监视父进程和子进程。如果子进程终止或父进程彼此终止,则会得到通知。 还有一个可用的调试器帮助器,它会自动将 VS 调试器附加到已启动的子进程

项目工地:

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

NuGet 软件包:

Https://www.nuget.org/packages/childprocesses Https://www.nuget.org/packages/childprocesses 视觉工作室调试/

这个问题的答案是从 @ Matt Howells 的绝妙回答和其他一些问题开始的(参见下面代码中的链接):

  • 支持32位和64位。
  • 修正了@Matt Howells 的回答中的一些问题:
    1. extendedInfoPtr的小内存泄漏
    2. “ Win32”编译错误,以及
    3. 在调用 CreateJobObject(使用 Windows10,VisualStudio2015,32位)时,我得到了一个堆栈不平衡异常。
  • 命名作业,因此如果使用 SysInternals,就可以很容易地找到它。
  • 具有更简单的 API 和更少的代码。

下面是如何使用这段代码:

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

要支持 Windows7,需要:

在我的例子中,我不需要支持 Windows7,所以我在 静态构造函数的顶部。

/// <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位结构的版本,方法是通过编程将托管版本和本机版本相互比较(总体大小以及每个成员的偏移量)。

我在 Windows7、8和10上测试过这段代码。

我正在寻找一个不需要非托管代码的解决方案。我也不能使用标准的输入/输出重定向,因为它是一个 Windows 窗体应用程序。

我的解决方案是在父进程中创建一个命名管道,然后将子进程连接到同一个管道。如果父进程退出,那么管道就会中断,子进程就可以检测到这一点。

下面是使用两个控制台应用程序的示例:

父母

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

使用 事件处理程序在一些退出场景中制作钩子:

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

只是我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);
}

迄今为止提出的解决方案丰富多彩,这是又一个补充... ..。

它们中的许多问题在于,它们依赖于父进程和子进程以一种有序的方式关闭,而当开发正在进行时,这种情况并不总是正确的。我发现,每当我在调试器中终止父进程时,我的子进程常常是孤立的,这需要我用 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();
};

对于这在生产代码中是否是可接受的实践,我有两种想法。一方面,这不应该发生。但另一方面,它可能意味着重新启动流程和重新启动生产服务器之间的区别。永远不该发生的事经常发生。

而且在调试有序关机问题时,它肯定非常有用。

对我有效的解决办法是:

创建进程时添加标记 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();

我也有同样的问题。我正在创建子进程,即使我的主应用程序崩溃了也不会被关闭。我必须在调试时手动销毁子进程。我发现没有必要让孩子们在某种程度上依赖父母。在我的主文件中,我添加了一个 try catch 来在 exit 时执行子进程的 CleanUp ()。

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

如果您能捕捉到情况,那么您的进程树应该在什么时候被杀死,因为。NET 5.0(.NET Core 3.0)你可以使用 Kill (bool whole ProcessTree)