必须先调一次 nextvalue()预热并等待100–500ms后再取值,否则结果为0或不准;避免ui线程高频轮询,推荐task.run+sleep控制节奏;监控自身进程需用processname而非"_total";高cpu不等于卡顿,需结合线程栈、gc、sql及外部依赖综合分析。

用 PerformanceCounter 实时读取 CPU 使用率,但别一上来就调 NextValue()
直接 new 一个 PerformanceCounter("Processor", "% Processor Time", "_Total") 然后马上调 NextValue(),大概率拿到 0 或不准确值——这是最常踩的坑。Windows 性能计数器是基于两次采样差值计算的,首次调用必须“预热”。
- 必须先调一次
NextValue()(不保存结果),等待至少 100–500ms 后再调第二次才有效 - 不要在 UI 线程里频繁轮询(比如每 100ms 刷新),会拖慢响应;建议用
Task.Run+Thread.Sleep(3000)控制节奏 -
"_Total"是全局 CPU,想监控当前程序自身,得用Process.GetCurrentProcess().ProcessName作为实例名 - 如果程序跑在容器或虚拟机里,
"_Total"仍反映宿主机整体负载,不是隔离视图
排查卡顿是否真由 CPU 引起?光看百分比不够
CPU 占用率 95% 不一定等于卡顿,100% 也不一定就是你代码的问题——可能是其他进程抢光了时间片,或者你的线程被系统调度长期挂起(比如等 I/O、锁、GC)。
- 用
Process Explorer双击进程 → 切到 Threads 页,按 CPU% 排序,看哪个线程持续高占用;右键 → Stack 查调用栈,重点盯住循环体、LINQ 多层嵌套、未设限的Parallel.ForEach - 在 Visual Studio 里用
Alt+F2打开性能探查器,选CPU 使用率,**必须用 Release 模式运行**,Debug 模式下 JIT 和调试符号会让数据完全失真 - 如果看到某个函数反复出现在栈顶,但源码里没明显死循环,检查是否触发了隐式装箱、正则回溯、或
ToString()被高频调用
监控要留痕,否则卡顿时根本回溯不了现场
任务管理器只能“看一眼”,而真实问题往往发生在凌晨三点、用户操作几秒后、或并发压测第 17 分钟——这时候你需要日志里带时间戳的 CPU 快照。
- 把
cpuCounter.NextValue()结果连同DateTime.Now、线程 ID、关键业务上下文(如“正在处理第 8421 条传感器数据”)一起写入本地文件或日志框架 - 别只记峰值,记录连续 5 次采样值,能区分是突发抖动还是持续过载
- 如果发现 CPU 高的同时
Working Set(工作集)也持续上涨,大概率是内存泄漏拖垮 GC,不是纯 CPU 问题
别忘了数据库和外部依赖也在吃 CPU
你的 C# 进程 CPU 很低,但整个机器卡成幻灯片?很可能是 SQL Server 或 Redis 正在后台疯狂计算。
- 在 C# 里执行 T-SQL 查询
sys.dm_exec_query_stats,抓出平均 CPU 最高的 TOP 10 查询,重点关注total_worker_time / execution_count - 用 WMI 或
PerformanceCounter("SQLServer:SQL Statistics", "Batch Requests/sec")监控 SQL Server 实例级吞吐,结合 CPU 曲线交叉判断 - 第三方 SDK(比如图像识别、加密库)常自带原生 CPU 密集型逻辑,它们不会出现在 .NET 调用栈里,但会拉高整体 CPU——用
Process Explorer的 CPU 列排序一眼就能揪出来
真正难的不是“怎么拿到 CPU 数值”,而是把数值和线程栈、GC 日志、SQL 查询、甚至硬件中断(DPC)关联起来。多数人卡在第一步,就以为问题解决了。











