首页 > 解决方案 > 应用程序挂起使用 WMI 读取当前 CPU ClockSpeed

问题描述

我从一个基本的 Winforms 应用程序开始,每三秒获取一次当前的 CPU 时钟速度,并用检索到的值刷新一个标签。

起初我使用了一个线程定时器,但我读到它是“不好的做法”,因为它消耗了一个线程。所以,我使用 BackgroundWorker 类来完成这项工作。不幸的是,该程序仍然每三秒冻结一次。

特此一些代码片段:

private void Form1_Load(object sender, EventArgs e)
{
    backgroundWorker1.RunWorkerAsync();
}

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    for (int i = 1; i <= 10; i++) {
        GenerateDelay(3000);
        backgroundWorker1.ReportProgress(i);
    }
}

private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    GetCurrentCpuSpeed();
}

private void GetCurrentCpuSpeed()
{
    ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT * FROM Win32_Processor");

    foreach (ManagementObject obj in searcher.Get()) {
        var clockSpeed = obj["CurrentClockSpeed"].ToString();
        lblCurrentClockSpeed.Text = clockSpeed;
    }
}

private void GenerateDelay(int miliseconds)
{
    Thread.Sleep(miliseconds);
}

我也尝试过这种方法作为一种变体:

private void GetCurrentCpuClockSpeed()
{
    using (ManagementClass mc = new ManagementClass("win32_processor"))
    {
        var instances = mc.GetInstances();

        foreach (var item in instances) {
            try {
                lblCurrentClockSpeed.Text = item.Properties["CurrentClockSpeed"].Value.ToString();
            }
            catch (Exception ex) {
                throw;
            }
        }
    }
}

编辑:根据评论更改代码以使用计时器。删除了我的“答案帖”。

以下是一些代码片段:

private void Form1_Load(object sender, EventArgs e)
{
    CreateAndStartTimer();
}

private void CreateAndStartTimer()
{
    System.Windows.Forms.Timer t = new System.Windows.Forms.Timer();
    t.Interval = 3000;
    t.Tick += new EventHandler(GetCurrentCpuSpeed2);
    t.Start();
}

private void GetCurrentCpuSpeed2(object sender, EventArgs e)
{
    ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT * FROM Win32_Processor");

    foreach (ManagementObject obj in searcher.Get()) {
        var clockSpeed = obj["CurrentClockSpeed"].ToString();

        lblCurrentClockSpeed.Text = "";
        lblCurrentClockSpeed.Text = clockSpeed;
    }
}

编辑2:

我让它工作了:)

特此我的代码片段:

private void Form1_Load(object sender, EventArgs e)
{
    CreateAndStartTimer();
}

private async void CreateAndStartTimer()
{
    // Create an AutoResetEvent to signal the timeout threshold in the
    // timer callback has been reached.
    var autoEvent = new AutoResetEvent(false);

    await Task.Run(() => new System.Threading.Timer(GetCurrentCpuClockSpeed, autoEvent, 1000, 1000));

}

private void GetCurrentCpuClockSpeed(object sender)
{
    bool isCallerOnDifferentThread = lblCurrentClockSpeed.InvokeRequired;

    ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT * FROM Win32_Processor");

    foreach (ManagementObject obj in searcher.Get()) {
        var clockSpeed = obj["CurrentClockSpeed"].ToString();

        if (isCallerOnDifferentThread) {
            lblCurrentClockSpeed.Invoke(new Action(() => { lblCurrentClockSpeed.Text = clockSpeed; }));
        }
    }
}

标签: c#winformswmibackgroundworker

解决方案


我测试了Microsoft.Management.Infrastructure(又名 WMI V2,您需要安装NuGet Package),而不是System.Management(您仍然可以使用它,但速度较慢),仅查询Win32_ProcessorCurrentClockSpeed

我认为返回查询值所需的时间对于使用非线程计时器(此处为System.Windows.Forms.Timer)是可以接受的。

在一台不太出色的测试机器(I5-4690K,16GB,无 SSD)中,查询返回027毫秒。

试一试:

这里,lblCpuClock是分配给接收 CPU 时钟值的标签的名称。

这里,Timer 在 Form 构造函数中初始化,在 Form 显示时启动,在 Form 关闭时停止。
如果您需要在其他时间启动它,请不要使用 Lambda 订阅Tick事件 ( cpuClockTimer.Tick += (s, e) => { ... }),使用适当的方法处理程序并在停止 Timer 时取消订阅事件。

public partial class SomeForm : Form
{
    System.Windows.Forms.Timer cpuClockTimer = null;

    public SomeForm()
    {
        InitializeComponent();
        // [...]

        if (this.components == null) this.components = new Container();
        cpuClockTimer = new System.Windows.Forms.Timer(this.components) { Interval = 1000 };

        cpuClockTimer.Tick += (s, e) => {
            uint? result = InfrastructureQueries.GetCurrentCpuClock(null);
            if (result.HasValue && !lblCpuClock.IsDisposed) {
                lblCpuClock.Text = $"Clock Speed: {result.Value} MHz";
            }
        };
    }

    protected override void OnShown(EventArgs e) {
        base.OnShown(e);
        cpuClockTimer.Enabled = true;
    }

    protected override void OnFormClosing(FormClosingEventArgs e) {
        cpuClockTimer.Enabled = false;
        base.OnFormClosing(e);
    }
}

否则,尝试启动一个任务,看看它是否更合适。

如前所述(在现在处于已删除状态的评论中),您可以使用 BackgroundWorker,但当然,不要从执行查询的方法中引用在 UI 线程中创建的控件;该方法只需要返回一个值,然后您可以ProgressChanged在引发事件时将其传递给事件处理程序。
该事件在 UI 线程中引发,因此您可以从处理程序安全地访问您的控件。

在这里,任务在显示表单时运行(在OnShown覆盖范围内)。如果您想按需运行它,请使用另
一种方法Task.Run(() => UpdateCpuClock(...)

用于ctsCpuClock.Cancel();随时取消任务。

public partial class SomeForm : Form
{
    static CancellationTokenSource ctsCpuClock = null;
    Progress<uint> ClockProgress = null;

    protected override void OnShown(EventArgs e) {
        base.OnShown(e);
        ctsCpuClock = new CancellationTokenSource();
        clockProgress = new Progress<uint>(value => {
            if (!lblCpuClock.IsDisposed) {
                lblCpuClock.Text = $"Clock Speed: {value} MHz";
            }
        });
        Task.Run(() => UpdateCpuClock(clockProgress, ctsCpuClock.Token));
    }

    protected override void OnFormClosing(FormClosingEventArgs e) {
        ctsCpuClock?.Cancel();
        ctsCpuClock?.Dispose();
        base.OnFormClosing(e);
    }

    private async Task UpdateCpuClock(IProgress<uint> progress, CancellationToken token) {
        while (!token.IsCancellationRequested) {
            uint? result = InfrastructureQueries.GetCurrentCpuClock(null);
            if (result.HasValue) {
                progress.Report(result.Value);
            }
            try { await Task.Delay(1000, token); }
            // Handle if you need logging, otherwise let go: your code called Cancel()
            catch (TaskCanceledException) { break; }
        }
    }
}

Management.Infrastructure工人方法

using System.Collections.Generic;
using System.Linq;
using Microsoft.Management.Infrastructure;

public class InfrastructureQueries
{
    private const string nameSpace = @"root\cimv2";

    private static CimSession CreateSession(string computerName)
    {
        if (string.IsNullOrEmpty(computerName) || 
            computerName.Equals("localhost", StringComparison.InvariantCultureIgnoreCase)) {
            computerName = null;
        }
        return CimSession.Create(computerName);
    }

    public static uint? GetCurrentCpuClock(string computerName = null)
    {
        var session = CreateSession(computerName);
        IEnumerable<CimInstance> query = session.QueryInstances(
            nameSpace, "WQL", "SELECT CurrentClockSpeed FROM Win32_Processor");

        return (uint?)query.FirstOrDefault()?.CimInstanceProperties["CurrentClockSpeed"].Value;
    }
}

推荐阅读