C# 系统 CPU 使用率和与 Windows 任务管理器同步

Ant*_*ony 5 c# windows multithreading cpu-usage

这是一个由两部分组成的问题,我想在堆栈上发布我的代码以帮助其他人完成相同的任务。

问题 1:

我有一个代码子集,我相信它可以根据测量间隔正确测量 CPU 使用率(跨系统中尽可能多的内核,根据检索的次数)——我在线程调用中使用了 1 秒。

我不得不从网络上的极少数文章和 C++ 代码中解读这一点。我的问题是,对于问题 1,我所做的是否正确?

有时返回的值是一个负数,这就是我乘以 -1 的原因。同样,我假设,因为文档很少,这就是我应该做的。

我有以下代码:

public static class Processor
{
    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool GetSystemTimes(out ComTypes.FILETIME lpIdleTime, out ComTypes.FILETIME lpKernelTime, out ComTypes.FILETIME lpUserTime);

    private static TimeSpan _sysIdleOldTs;
    private static TimeSpan _sysKernelOldTs;
    private static TimeSpan _sysUserOldTs;

    static Processor()
    {
    }

    public static void Test()
    {
        ComTypes.FILETIME sysIdle, sysKernel, sysUser;

        if(GetSystemTimes(out sysIdle, out sysKernel, out sysUser))
        {
            TimeSpan sysIdleTs = GetTimeSpanFromFileTime(sysIdle);
            TimeSpan sysKernelTs = GetTimeSpanFromFileTime(sysKernel);
            TimeSpan sysUserTs = GetTimeSpanFromFileTime(sysUser);

            TimeSpan sysIdleDiffenceTs = sysIdleTs.Subtract(_sysIdleOldTs);
            TimeSpan sysKernelDiffenceTs = sysKernelTs.Subtract(_sysKernelOldTs);
            TimeSpan sysUserDiffenceTs = sysUserTs.Subtract(_sysUserOldTs);

            _sysIdleOldTs = sysIdleTs;
            _sysKernelOldTs = sysKernelTs;
            _sysUserOldTs = sysUserTs;

            TimeSpan system = sysKernelDiffenceTs.Add(sysUserDiffenceTs);

            Double cpuUsage = (((system.Subtract(sysIdleDiffenceTs).TotalMilliseconds) * 100) / system.TotalMilliseconds);

            if (cpuUsage < 0)
            {
                Console.WriteLine("CPU: " + ((int) (cpuUsage)*-1) + "%");
            }
            else
            {
                Console.WriteLine("CPU: " + (int) (cpuUsage) + "%");
            }
            Console.WriteLine("");
        }
        else
        {
            Console.WriteLine("Couldn't get CPU usage!");
            Console.WriteLine("");
        }
    }

    private static TimeSpan GetTimeSpanFromFileTime(ComTypes.FILETIME time)
    {
        return TimeSpan.FromMilliseconds((((ulong)time.dwHighDateTime << 32) + (uint)time.dwLowDateTime) * 0.000001);
    }
}
Run Code Online (Sandbox Code Playgroud)

问题2:

无论如何,我是否可以在我的程序中将一个线程与 Windows 任务管理器的线程同步,以便将测量数字(例如 CPU 使用率)与上述代码相匹配?

我的意思是,如果你打开 Windows 任务管理器,你会注意到它每秒轮询一次——实际上它不需要少于这个。我想做的是将时间与我的线程相匹配。

所以当 Windows 任务管理器轮询时,我的线程轮询。


一些注意事项:

我不想使用性能计数器或 .NET 内置方法。事实上,我相信 - 从我读过的内容来看,.NET 没有计算机器上 CPU 使用率的方法,否则需要性能计数器。

性能计数器有开销,此外还会使 GC 增长,更不用说调用下一个 result 的延迟了。虽然我的软件不需要具有实时性能,但我确实需要它具有响应性并尽可能少地使用 CPU 时间。可以在不到一毫秒的时间内调用和返回上述代码。事实上,在我的开发机器上,时间跨度差异显示为 0ms。我不相信性能计数器响应迅速。

如果您好奇,我的软件正在收集许多项目,CPU、内存、事件日志项目等,其中所有这些都需要在 SQL CE 中收集并存储在 SQL CE 中,在下一次轮询之前,1 秒后。然而,每个任务、项目都在自己的线程上,以促进这一点。

此外,上面的代码并没有以任何方式进行优化,您会注意到我还没有对其进行评论。原因是我想在优化等之前确保它是正确的。

更新 1

根据我发表的评论,我删除了额外的“系统”时间跨度,因为它不是必需的,并修改了检索“CPU 使用率”的行并进行了适当的转换。

int cpuUsage = (int)(((sysKernelDifferenceTs.Add(sysUserDifferenceTs).Subtract(sysIdleDifferenceTs).TotalMilliseconds) * 100.00) / sysKernelDifferenceTs.Add(sysUserDifferenceTs).TotalMilliseconds);
Run Code Online (Sandbox Code Playgroud)

虽然我仍然不确定公式。虽然它似乎非常准确,但它有时会返回一个负数,这就是为什么我将它乘以 -1(如果是这种情况)。毕竟,没有 -2% CPU 使用率之类的东西。

更新 2

所以我使用“System.Diagnostics.PerformanceCounter”做了一个简单的测试。虽然非常方便并且完全符合它的意图,但它确实会产生开销。

以下是我的观察:

  • 性能计数器的初始化时间要长得多。在我的 i7 2.6 Ghz 上大约长了三秒。
  • 性能计数器似乎也仅仅通过使用它就增加了大约 5MB 的 RAM 使用量。我的意思是:使用上面的代码,我的应用程序最大内存为 7.5MB。使用性能计数器,它“开始”为 12.5MB。
  • 在 5 秒的时间内,我的线程运行了 5 次 - 每秒一次,我的应用程序的内存增加了 1 MB,这种增加与时间一致,尽管它确实稳定了,无论如何,在我的情况下,3-4MB以上开始。所以我的应用程序通常是 7.5MB 内存,上面的代码,PC 代码稳定在 16.5 MB 内存 - 比上面的代码增加了 9MB。注意:上面的代码不会导致这种增加。

因此,如果您的应用程序是以资源使用和时间是关键的方式构建的,由于这些原因,我建议不要使用性能计数器。否则继续前进,因为它没有任何混乱。

至于我的应用程序,性能计数器将不利于我的软件的目的。

sel*_*bie 1

我认为你的公式中有一个错误。您希望基本上计算 CPU 使用率,如下所示:

CPU Usage = KernelTimeDiff + UserTimeDiff
            --------------------------------------------
            KernelTimeDiff + UserTimeDiff + IdleTimeDiff
Run Code Online (Sandbox Code Playgroud)

因此,对代码进行快速修改如下:

        //  TimeSpan system = sysKernelDiffenceTs.Add(sysUserDiffenceTs);
        //Double cpuUsage = (((system.Subtract(sysIdleDiffenceTs).TotalMilliseconds) * 100) / system.TotalMilliseconds); 

        TimeSpan totaltime = sysKernelDiffenceTs.Add(sysUserDiffenceTs);
        totaltime = totaltime.Add(sysIdleDifferenceTs);
        int cpuUsage = 100 - (sysIdleDifferenceTs.TotalMilliseconds * 100) / totaltime.TotalMilliseconds;
       Console.WriteLine("CPU: " + cpuUsage + "%");
Run Code Online (Sandbox Code Playgroud)

您最初将 cpuUsage 声明为“Double”。我不确定您是否想要浮点精度,但在您的代码中,您肯定没有得到除整数精度之外的任何东西,因为赋值语句只是进行整数数学运算。如果您需要更高的计算精度,您可以通过混合一些浮点来轻松获得它:

Double cpuUsage = 100.0 - (sysIdleDifferenceTs.TotalMilliseconds * 100.0) /totaltime.TotalMilliseconds;
Run Code Online (Sandbox Code Playgroud)

另外,关于与任务管理器同步。据我了解,任务管理器使用性能计数器。(我怀疑 GetSystemTimes 正在幕后进行性能计数器调用,但也许不是)。我也不知道为什么你不使用性能计数器。“%处理时间”计数器是一个即时样本计数器,不需要计算与先前结果的差异。(每个逻辑 CPU 有一个)。使用 PDH 帮助程序函数而不是旧的注册表项 API 来获取它。您可以从非托管 C/C++ DLL 中执行此操作,该 DLL 将“GetCpuUsage”函数导出回 C# 代码。但我也不知道为什么你不能直接从 C# 调用 PDH 函数。我不知道你所说的这个开销。我也不确定我是否理解您提到的“调用下一个结果的延迟”。