Process.TotalProcessorTime返回的是进程累计CPU时间,非实时百分比;正确方法需同时采集进程与系统总CPU时间并归一化计算,推荐用PerformanceCounter或WMI实现。
Process.TotalProcessorTime 返回的不是实时CPU百分比
很多人直接用 process.totalprocessortime 减两次差值再除以时间间隔,以为能算出cpu占用率——这是错的。它返回的是进程累计使用的cpu时间(timespan),不是百分比,也不包含系统总cpu时间基准,单独用它没法得出“占用了多少%的cpu”。
正确做法是:必须同时采集进程的CPU时间和系统整体的CPU时间(通过 PerformanceCounter 或 WMI),做归一化计算。Windows本身没有提供单进程“瞬时CPU%”的轻量API,所有靠谱方案都绕不开采样+对比。
- 采样间隔至少 500ms,太短会导致精度崩坏(尤其多核下)
-
PerformanceCounter初始化慢、首次读值常为0或报错,需加NextValue()预热 - 不同Windows版本对
"Process\% Processor Time"计数器的支持略有差异,Win10/11基本稳定,Server 2012+建议用WMI兜底
用 PerformanceCounter 获取进程CPU使用率(推荐方案)
这是最常用、开销可控、.NET原生支持的方式。关键在于选对计数器路径和实例名,并处理好进程生命周期变化。
示例逻辑:
var counter = new PerformanceCounter(
"Process",
"% Processor Time",
"notepad"); // 进程名,不含.exe
counter.NextValue(); // 预热,丢弃第一次无效值
Thread.Sleep(500);
float cpuPercent = counter.NextValue(); // 实际值,单位是 % * 100(即 12.34 表示 12.34%)- 实例名填进程主模块名(如
chrome、devenv),不是窗口标题,也不是完整路径 - 若进程已退出,
NextValue()会抛InvalidOperationException,必须 try-catch - 同一进程多个实例(如多个 chrome.exe)会汇总到一个计数器里;要区分具体实例,得用 WMI 或遍历
Process.GetProcessesByName()+ PID 绑定 - 计数器名称大小写敏感,
"% Processor Time"中的空格和%不能少
用 WMI 查询指定PID的CPU使用率(跨版本兼容更强)
当 PerformanceCounter 在某些环境(如容器、低权限服务账户)下不可用时,WMI是更鲁棒的选择,但性能略低、查询稍慢。
核心是查 Win32_PerfFormattedData_PerfProc_Process 类,过滤 IdProcess 字段:
var query = $"SELECT PercentProcessorTime FROM Win32_PerfFormattedData_PerfProc_Process WHERE IdProcess = {pid}";
using var searcher = new ManagementObjectSearcher(query);
foreach (ManagementObject obj in searcher.Get()) {
int pct = Convert.ToInt32(obj["PercentProcessorTime"]);
}- 返回值已是百分比整数(0–100),无需再除以核数或归一化
- WMI查询可能超时,建议设
searcher.Options.Timeout = TimeSpan.FromSeconds(2) - 需要引用
System.Management,.NET Core/.NET 5+ 需额外安装System.ManagementNuGet 包 - 注意:WMI在Windows Server Core或Nano Server上默认禁用,生产环境需确认策略
监控多进程时别漏掉“进程名冲突”和“PID复用”问题
用名字查进程(如 Process.GetProcessesByName("chrome"))再拿PID去配CPU数据,看似自然,实则埋了两个坑:
- 多个同名进程存在时,
GetProcessesByName返回数组,但你可能只取了第一个,结果监控对象错位 - 进程退出后PID可能被新进程快速复用,若你缓存了旧PID对应的
PerformanceCounter实例,后续读出来的就是另一个程序的数据 - 更稳的做法:启动监控时记录进程的
StartTime和Id,每次采样前先用Process.GetProcessById(pid)确认是否还存活且StartTime一致 - 如果目标程序启动频繁(如CI任务中的临时进程),建议放弃名字匹配,改用启动命令行关键字或父进程关系来识别
实际跑起来你会发现,最难的不是算百分比,而是让“哪个进程”的定义在整段时间内保持稳定。名字会重、PID会复用、计数器实例会失效——这些边界情况不处理,监控数据看起来平滑,其实全是噪声。











