如何杀死程序意外终止后遗留的Excel子进程?
问题
使用Microsoft.Office.Interop.Excel.Application执行MyMainFunction()时,若在执行excelApp = new Microsoft.Office.Interop.Excel.Application();后立刻意外崩溃,此时PID还未完成注册,现有通过留存PID来杀死进程的方法就无法清理遗留的孤立Excel进程。询问是否存在更简便的解决方案?
现有实现代码
public class TestClass { [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); System.Collections.Concurrent.ConcurrentDictionary<uint, byte> pidDictionary = new System.Collections.Concurrent.ConcurrentDictionary<uint, byte>(); public TestClass() { } ~TestClass() { KillRemainingProcesses(); } private void RegistPID(uint pid) { pidDictionary.TryAdd(pid, 0); } private void KillExcelAndRemove(uint processId) { try { var process = Process.GetProcessById((int)processId); if (process != null && !process.HasExited && process.ProcessName.Equals("EXCEL", StringComparison.OrdinalIgnoreCase)) { process.Kill(); pidDictionary.TryRemove(processId, out byte notUse); } } catch (Exception ex) { Console.WriteLine($"{ex.Message}, Kill Error"); } } private void KillRemainingProcesses() { foreach (var processId in pidDictionary.Keys) { try { Process process = Process.GetProcessById((int)processId); if (process != null && !process.HasExited && process.ProcessName.Equals("EXCEL", StringComparison.OrdinalIgnoreCase)) { process.Kill(); } } catch (Exception ex) { Console.WriteLine(ex.Message); } } } public void MyMainFunction() { Microsoft.Office.Interop.Excel.Application excelApp = null; Microsoft.Office.Interop.Excel.Workbook workbook = null; Microsoft.Office.Interop.Excel.Worksheet worksheet = null; uint processId = 0; try { excelApp = new Microsoft.Office.Interop.Excel.Application(); GetWindowThreadProcessId(new IntPtr(excelApp.Hwnd), out processId); RegistPID(processId); // Do Excel Edit workbook.Save(); } catch (Exception ex) { } finally { if (worksheet != null) { Marshal.ReleaseComObject(worksheet); worksheet = null; } if (workbook != null) { workbook.Close(false); Marshal.ReleaseComObject(workbook); workbook = null; } if (excelApp != null) { excelApp.Quit(); Marshal.ReleaseComObject(excelApp); excelApp = null; } GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); GC.WaitForPendingFinalizers(); KillExcelAndRemove(processId); } } }
更简便的解决方案:利用Windows Job Object
相比PID留存方案,Windows Job Object是更可靠的解决方式,无需手动跟踪PID,能在父进程崩溃时自动终止所有关联的子进程,核心逻辑如下:
- 创建Job对象:调用Windows API创建Job对象,配置
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE规则,该规则会让Job在自身被关闭(父进程退出/崩溃时)自动杀死所有关联进程。 - 关联Excel进程:创建Excel实例后,立即将其进程句柄加入到这个Job中。
这种方式完全规避了PID未注册就崩溃的问题,只要Excel进程被加入Job,无论父进程何时崩溃,系统都会自动清理该Excel进程。
实现核心代码示例:
// 定义Windows Job相关API [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] static extern IntPtr CreateJobObject(IntPtr lpJobAttributes, string name); [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 hJob, IntPtr hProcess); public enum JobObjectInfoType { JobObjectExtendedLimitInformation = 9 } [StructLayout(LayoutKind.Sequential)] 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)] 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)] struct IO_COUNTERS { public ulong ReadOperationCount; public ulong WriteOperationCount; public ulong OtherOperationCount; public ulong ReadTransferCount; public ulong WriteTransferCount; public ulong OtherTransferCount; } const uint JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x2000; // 修改后的Main函数 public void MyMainFunction() { Microsoft.Office.Interop.Excel.Application excelApp = null; IntPtr jobHandle = IntPtr.Zero; try { // 创建Job对象并配置自动清理规则 jobHandle = CreateJobObject(IntPtr.Zero, null); JOBOBJECT_EXTENDED_LIMIT_INFORMATION info = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION(); info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; int infoSize = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION)); IntPtr infoPtr = Marshal.AllocHGlobal(infoSize); Marshal.StructureToPtr(info, infoPtr, false); SetInformationJobObject(jobHandle, JobObjectInfoType.JobObjectExtendedLimitInformation, infoPtr, (uint)infoSize); Marshal.FreeHGlobal(infoPtr); // 创建Excel实例 excelApp = new Microsoft.Office.Interop.Excel.Application(); // 获取Excel进程句柄并关联到Job uint processId = 0; GetWindowThreadProcessId(new IntPtr(excelApp.Hwnd), out processId); Process excelProcess = Process.GetProcessById((int)processId); AssignProcessToJobObject(jobHandle, excelProcess.Handle); // 执行Excel操作 // workbook.Save(); } catch (Exception ex) { // 异常处理逻辑 } finally { // 释放Excel资源(原有逻辑保留) if (excelApp != null) { excelApp.Quit(); Marshal.ReleaseComObject(excelApp); } // 关闭Job对象,未退出的关联进程会被自动杀死 if (jobHandle != IntPtr.Zero) { CloseHandle(jobHandle); } GC.Collect(); GC.WaitForPendingFinalizers(); } } [DllImport("kernel32.dll", SetLastError = true)] static extern bool CloseHandle(IntPtr hObject);
这种方案无需手动维护PID列表,也不用担心崩溃时PID未注册的问题,是目前处理此类子进程清理最可靠的简便方案。
内容的提问来源于stack exchange,提问作者김지훈
相关产品推荐
相关产品推荐

