获取已退出子进程内存使用情况遇System.InvalidOperationException异常求助
解决进程退出后无法获取PeakWorkingSet64的问题
咱先搞清楚你为啥遇到这个报错:当进程彻底退出后,系统会立刻回收它的大部分内核资源,这时候System.Diagnostics.Process对象就没法再读取PeakWorkingSet64这类性能数据了——这就是那个InvalidOperationException的根源。
既然你对准确性和效率要求很高,轮询肯定不是最优解,下面两个非轮询的方案完全符合你的需求:
方案1:监听进程退出事件,在资源回收前瞬间捕获峰值
这个方案是事件驱动的,完全不需要轮询,效率拉满。核心思路是给子进程注册Exited事件,在进程刚退出但系统还没来得及回收资源的瞬间,立刻读取PeakWorkingSet64。
直接上代码示例:
using System; using System.Diagnostics; class ProcessMemoryTracker { private static long _peakMemoryBytes; static void Main(string[] args) { // 配置子进程启动信息 var startInfo = new ProcessStartInfo("your-child-process.exe") { UseShellExecute = false, RedirectStandardOutput = true }; using var childProcess = new Process(); childProcess.StartInfo = startInfo; childProcess.EnableRaisingEvents = true; // 必须开启事件触发,否则Exited事件不会生效 childProcess.Exited += OnChildProcessExited; // 启动子进程 childProcess.Start(); // 如果需要等待子进程完成,这里可以用WaitForExit,但Exited事件会在退出时自动触发 childProcess.WaitForExit(); Console.WriteLine($"子进程峰值工作集内存:{_peakMemoryBytes} 字节"); } private static void OnChildProcessExited(object sender, EventArgs e) { if (sender is Process process) { try { // 此时进程刚退出,资源还没被回收,能成功读取峰值内存 _peakMemoryBytes = process.PeakWorkingSet64; } catch (Exception ex) { // 极端场景下可能出现异常,这里可以加容错逻辑 Console.WriteLine($"捕获峰值内存失败:{ex.Message}"); } } } }
这里的关键是EnableRaisingEvents = true——如果不设置这个,Exited事件根本不会触发,一定要记得加。
方案2:用Performance Counter跟踪(支持进程退出后读取)
如果你需要更全面的内存监控,或者担心事件触发时机的极端情况,可以用PerformanceCounter。它会保留进程的统计数据一段时间,哪怕进程退出了,也能读到峰值内存。
示例代码:
using System; using System.Diagnostics; class PerformanceCounterMemoryTracker { static void Main(string[] args) { using var childProcess = Process.Start("your-child-process.exe"); if (childProcess == null) { Console.WriteLine("启动子进程失败"); return; } // 创建针对当前子进程的峰值工作集计数器 // 注意:如果有同名进程,要用进程ID作为实例名,避免混淆 using var peakCounter = new PerformanceCounter( categoryName: "Process", counterName: "Peak Working Set", instanceName: childProcess.Id.ToString(), readOnly: true ); // 等待子进程退出 childProcess.WaitForExit(); // 进程退出后仍能读取计数器的值 long peakMemoryBytes = (long)peakCounter.NextValue(); Console.WriteLine($"子进程峰值工作集内存:{peakMemoryBytes} 字节"); } }
这个方案的优势是哪怕进程退出一小会儿,只要系统还没清理掉性能计数器的缓存,就能拿到数据,容错性更强。
最后提几个注意点
- 不管用哪个方案,都要记得用
using语句或者手动调用Dispose()释放Process和PerformanceCounter对象,避免资源泄漏。 - 如果你用的是.NET Core/.NET 5+,这些API完全兼容,跨平台也能用。
- 方案1的事件触发时机非常早,几乎不会出现拿不到数据的情况,是效率最高的选择。
内容的提问来源于stack exchange,提问作者victor12369




