通过任务管理器终止Windows服务父进程时如何同步杀死其子进程
解决方案
方案1:使用Windows Job Object(推荐,无需修改子进程,自动关联所有后代进程)
这是Windows内核原生提供的进程组管理机制,你可以在父服务进程中创建作业对象,设置JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE标记,将父进程自身加入该作业。当父进程被任务管理器终止时,父进程持有的作业对象句柄会被系统自动释放,触发作业规则,自动终止所有关联在该作业内的进程,包括你启动的子进程,甚至子进程后续启动的孙进程也会被同步清理。
修改后的父服务代码如下:
using System.Diagnostics; using System.Runtime.InteropServices; using Microsoft.Extensions.Hosting; // parent service class Program { // 导入Win32 API [DllImport("kernel32.dll", CharSet = CharSet.Auto)] private static extern IntPtr CreateJobObject(IntPtr lpJobAttributes, string lpName); [DllImport("kernel32.dll")] private static extern bool SetInformationJobObject(IntPtr hJob, JobObjectInfoType infoType, IntPtr lpJobObjectInfo, uint cbJobObjectInfoLength); [DllImport("kernel32.dll", SetLastError = true)] private static extern bool AssignProcessToJobObject(IntPtr hJob, IntPtr hProcess); private enum JobObjectInfoType { ExtendedLimitInformation = 9 } [StructLayout(LayoutKind.Sequential)] private 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; } [StructLayout(LayoutKind.Sequential)] private struct JOBOBJECT_BASIC_LIMIT_INFORMATION { public long PerProcessUserTimeLimit; public long PerJobUserTimeLimit; public uint LimitFlags; public UIntPtr MinimumWorkingSetSize; public UIntPtr MaximumWorkingSetSize; public uint ActiveProcessLimit; public UIntPtr Affinity; public uint PriorityClass; public uint SchedulingClass; } [StructLayout(LayoutKind.Sequential)] private struct IO_COUNTERS { public ulong ReadOperationCount; public ulong WriteOperationCount; public ulong OtherOperationCount; public ulong ReadTransferCount; public ulong WriteTransferCount; public ulong OtherTransferCount; } // 作业对象句柄,需要保持全局引用避免被GC回收 private static IntPtr _jobHandle; static void Main(string[] args) { // 初始化作业对象 _jobHandle = CreateJobObject(IntPtr.Zero, null); var extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION(); // 设置关闭作业时终止所有关联进程的标记 extendedInfo.BasicLimitInformation.LimitFlags = 0x2000; // JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE int length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION)); IntPtr extendedInfoPtr = Marshal.AllocHGlobal(length); Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false); SetInformationJobObject(_jobHandle, JobObjectInfoType.ExtendedLimitInformation, extendedInfoPtr, (uint)length); // 将父进程自身加入作业,后续启动的子进程会自动继承作业关联,无需额外手动添加 AssignProcessToJobObject(_jobHandle, Process.GetCurrentProcess().Handle); Marshal.FreeHGlobal(extendedInfoPtr); var host = Host .CreateDefaultBuilder(args) .UseWindowsService() .Build(); var startInfo = new ProcessStartInfo { FileName = "Test.ChildProcessApp.exe" }; Process.Start(startInfo); host.Run(); } }
方案2:父进程监听退出事件主动清理子进程
如果不想调用Win32 API,可以在父进程启动子进程时记录所有子进程实例,注册父进程的退出/中止事件,在事件触发时遍历杀掉所有记录的子进程。这个方案的缺陷是如果父进程被极端强制结束,可能来不及触发清理逻辑,导致子进程残留。
核心代码片段如下:
using System.Diagnostics; using Microsoft.Extensions.Hosting; // parent service class Program { // 存储所有启动的子进程实例 private static List<Process> _childProcesses = new List<Process>(); static void Main(string[] args) { // 注册进程退出事件 AppDomain.CurrentDomain.ProcessExit += (s, e) => { foreach (var p in _childProcesses) { if (!p.HasExited) { p.Kill(); p.WaitForExit(); } } }; var host = Host .CreateDefaultBuilder(args) .UseWindowsService() .Build(); var startInfo = new ProcessStartInfo { FileName = "Test.ChildProcessApp.exe" }; var child = Process.Start(startInfo); _childProcesses.Add(child); host.Run(); } }
注意事项
- 方案1是最优选择,兼容所有Windows主流版本,无论父进程是正常退出还是被强制终止,都能保证子进程被同步清理,且不需要额外维护子进程列表。
- Windows服务默认运行在Session 0,以上两个方案在Session 0环境下均可以正常生效。
内容的提问来源于stack exchange,提问作者GY_
相关产品推荐
相关产品推荐

