导读:本期聚焦于书生创作的《C#怎么获取当前CPU占用率?PerformanceCounter使用详解与避坑指南》,敬请观看详情。不少人在写Windows桌面监控工具时卡在了一个地方:明明系统任务管理器能显示CPU使用率,自己用C#读出来却一直是零或者跳变剧烈。其实问题大多出在PerformanceCounter的采样方式上。这个类直连系统性能计数器,但首次读取会返回空值,而且必须间隔一段时间取两次差值才算真实占用。本文梳理了在.NET Framework与.NET Core里配置CategoryName、CounterName、InstanceName的正确写法,也给出了跨平台不能用的替代思路。弄清NextValue的调用节奏,才能拿到稳定数据。

在Windows平台上做资源监控,C#开发者最常碰到的需求之一就是读取当前CPU占用率。系统自带的性能计数器体系记录了大量运行时指标,而.NET提供的PerformanceCounter类正是访问这些指标的官方通道。理解它的工作机制,比直接复制网上的代码片段更重要,因为错误的调用顺序会让你的程序一直输出0或者毫无规律的数值。

C#怎么获取当前CPU占用率?PerformanceCounter使用详解与避坑指南

PerformanceCounter基本工作原理与核心属性

PerformanceCounter位于System.Diagnostics命名空间,它本质上是对Windows性能数据提供者(PDH)的一个托管封装。当我们创建一个实例时,需要指定三个关键参数:类别名(CategoryName)、计数器名(CounterName)以及可选的实例名(InstanceName)。对于整体CPU占用率,类别名固定为Processor,计数器名为% Processor Time,实例名用通配符_Total表示所有核心的合计值。

很多人忽略的一点是,性能计数器并不是实时快照。Windows以一定频率采样并更新内核中的计数器数据,而PerformanceCounter.NextValue方法返回的是自上次调用以来该计数器变化的折算值。因此第一次调用NextValue时,由于之前没有基准,往往返回0。只有间隔至少一秒再次调用,才能算出这段时间内的平均占用。这个设计决定了任何正确的CPU占用率读取都必须包含两次采样。

除了上述三个属性,MachineName允许指向远程机器,但需要确保权限与防火墙配置。ReadOnly属性一般保持为true即可,因为监控不需要写回计数器。在.NET Framework中这些类型开箱即用,但在.NET Core 3.0之后,Windows专属的PerformanceCounter仍然可用,只是必须运行在Windows系统上,跨平台编译时会提示不支持。

using System;
using System.Diagnostics;

class Demo
{
    static void Main()
    {
        // 创建整体CPU计数器
        PerformanceCounter cpuCounter = new PerformanceCounter(
            "Processor",       // 类别
            "% Processor Time", // 计数器
            "_Total");          // 实例,合计所有核心

        // 第一次调用通常返回0
        float first = cpuCounter.NextValue();
        Console.WriteLine("首次读取: " + first);

        // 等待一秒让系统采样
        System.Threading.Thread.Sleep(1000);

        // 第二次读取才是真实占用率
        float second = cpuCounter.NextValue();
        Console.WriteLine("一秒后CPU占用: " + second + "%");
    }
}

获取单进程与多核CPU占用的代码实践

除了整机CPU,很多时候我们要监控的是某个进程的CPU消耗。此时类别名变为Process,计数器名仍是% Processor Time,但实例名必须是进程名(不带exe后缀)或者进程ID对应的实例。如果系统里同名进程有多个,PerformanceCounter会自动附加编号,例如chrome#1、chrome#2,这要求我们在代码里用Process.GetProcessById配合InstanceName的查找来匹配。

多核机器的单进程占用率有个坑:任务管理器里看到的进程CPU百分比是基于所有逻辑处理器之和的,而PerformanceCounter返回的值在多线程满负荷时可能超过100%。如果需要和任务管理器一致,应该除以逻辑核心数。下面示例展示如何读取指定进程名的平均占用,并做了简单的容错。

对于多实例场景,可以使用PerformanceCounterCategory.GetInstanceNames拿到全部实例,再逐一构造计数器。这种写法在写资源监视器类工具时很常见,但要控制采样频率,否则频繁创建对象会带来自身开销,反而干扰监控准确性。

using System;
using System.Diagnostics;

class ProcessCpu
{
    static void Main()
    {
        string procName = "dotnet";
        // 获取该进程类别下的所有实例名
        var cat = new PerformanceCounterCategory("Process");
        string[] instances = cat.GetInstanceNames();
        foreach (var inst in instances)
        {
            if (inst.StartsWith(procName))
            {
                PerformanceCounter pc = new PerformanceCounter(
                    "Process", "% Processor Time", inst);
                pc.NextValue();
                System.Threading.Thread.Sleep(500);
                float val = pc.NextValue();
                // 除以逻辑核心数得到类似任务管理器的百分比
                int cores = Environment.ProcessorCount;
                Console.WriteLine(inst + " 占用: " + (val / cores) + "%");
            }
        }
    }
}

常见误区、异常处理与跨平台替代方案

第一个典型误区是只调用一次NextValue就当作当前值,结果界面上永远显示0,用户以为代码失效。第二个误区是采样间隔太短,比如sleep 10毫秒,这样计数器还没更新,第二次值也接近0,曲线变成噪声。官方建议最短间隔200毫秒,实际监控用500毫秒到1秒最平稳。

异常处理方面,如果指定的进程已退出,PerformanceCounter会抛出InvalidOperationException。在生产代码里应该用try-catch包裹,并在界面层做降级显示。另外某些受限环境(如普通用户权限的共享主机)可能禁止访问性能计数器,此时要考虑改用WMI或者Task Manager提供的接口,不过那些方案复杂度更高。

跨平台需求现在越来越普遍。PerformanceCounter只在Windows实现,在Linux或macOS上构造会抛PlatformNotSupportedException。如果项目需要跨平台监控,应该抽象出接口,在Windows下用PerformanceCounter,在Linux下读取/proc/stat文件计算差值,在macOS下调用sysctl或ps命令。这样上层业务逻辑不用关心底层差异,也避免了部署到容器里直接崩溃。

using System;
using System.Diagnostics;

class SafeCpu
{
    static void Main()
    {
        PerformanceCounter counter = null;
        try
        {
            counter = new PerformanceCounter("Processor", "% Processor Time", "_Total");
            counter.NextValue();
            System.Threading.Thread.Sleep(1000);
            float v = counter.NextValue();
            Console.WriteLine("安全读取CPU: " + v + "%");
        }
        catch (PlatformNotSupportedException)
        {
            Console.WriteLine("当前系统不支持PerformanceCounter,请使用跨平台方案");
        }
        catch (InvalidOperationException ex)
        {
            Console.WriteLine("计数器访问失败: " + ex.Message);
        }
        finally
        {
            counter?.Dispose();
        }
    }
}

把以上几部分组合起来,一个健壮的C# CPU占用率监控模块就清晰了。重点记住两次采样、合理间隔、实例名匹配以及平台限制,就能避开绝大多数坑。对于长期运行的服务,建议把计数器对象缓存复用,而不是每次读取都新建,既减少资源消耗也避免计数器泄漏。

C#PerformanceCounterCPU占用率修改时间:2026-08-18 22:18:38

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。