1

我一直在遵循WPF 线程模型指南来创建一个小应用程序,该应用程序将监视和显示当前和峰值 CPU 使用率。但是,当我在事件处理程序中更新我的当前 CPU 和峰值 CPU 时,我的窗口中的数字根本不会改变。调试时,我可以看到文本字段确实发生了变化,但在窗口中没有更新。

我听说构建这样的应用程序是不好的做法,应该改用 MVVM 方法。事实上,一些人对我能够在没有运行时异常的情况下运行它感到惊讶。无论如何,我想从第一个链接中找出代码示例/指南。

让我知道你的想法是什么!

这是我的xaml:

<Window x:Class="UsagePeak2.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="CPU Peak" Height="75" Width="260">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" >
    <Button Content="Start"  
        Click="StartOrStop"
        Name="startStopButton"
        Margin="5,0,5,0"
        />
    <TextBlock Margin="10,5,0,0">Peak:</TextBlock>
    <TextBlock Name="CPUPeak" Margin="4,5,0,0">0</TextBlock>
    <TextBlock Margin="10,5,0,0">Current:</TextBlock>
    <TextBlock Name="CurrentCPUPeak" Margin="4,5,0,0">0</TextBlock>
</StackPanel>

这是我的代码

public partial class MainWindow : Window
{
    public delegate void NextCPUPeakDelegate();

    double thisCPUPeak = 0;

    private bool continueCalculating = false;

    PerformanceCounter cpuCounter;

    public MainWindow() : base()
    {
        InitializeComponent();
    }

    private void StartOrStop(object sender, EventArgs e)
    {
        if (continueCalculating)
        {
            continueCalculating = false;
            startStopButton.Content = "Resume";
        }
        else
        {
            continueCalculating = true;
            startStopButton.Content = "Stop";
            startStopButton.Dispatcher.BeginInvoke(
                DispatcherPriority.Normal, new NextCPUPeakDelegate(GetNextPeak));
            //GetNextPeak();
        }
    }

    private void GetNextPeak()
    {

        cpuCounter = new PerformanceCounter("Processor", "% Processor Time", "_Total");

        double currentValue = cpuCounter.NextValue();

        CurrentCPUPeak.Text = Convert.ToDouble(currentValue).ToString();

        if (currentValue > thisCPUPeak)
        {
            thisCPUPeak = currentValue;
            CPUPeak.Text = thisCPUPeak.ToString();
        }

        if (continueCalculating)
        {
            startStopButton.Dispatcher.BeginInvoke(
                System.Windows.Threading.DispatcherPriority.SystemIdle,
                new NextCPUPeakDelegate(this.GetNextPeak));
        }
    }
}
4

2 回答 2

4

这里有几个问题。首先,您正在主线程上工作,但您正在以一种非常迂回的方式进行工作。正是这里的这一行使您的代码具有响应性:

startStopButton.Dispatcher.BeginInvoke(
    System.Windows.Threading.DispatcherPriority.SystemIdle,
    new NextCPUPeakDelegate(this.GetNextPeak));

BeginInvoke方法文档(强调我的):

在与 Dispatcher 关联的线程上以指定的优先级异步执行指定的委托。

即使您以很高的速率发送此消息,您也会通过在后台线程上将帖子返回到消息循环中来对 UI 线程上的工作进行排队。这就是让你的 UI 完全响应的原因。

也就是说,您想像这样重组您的GetNextPeak方法:

private Task GetNextPeakAsync(CancellationToken token)
{
    // Start in a new task.
    return Task.Factory.StartNew(() => {
        // Store the counter outside of the loop.
        var cpuCounter = 
            new PerformanceCounter("Processor", "% Processor Time", "_Total");

        // Cycle while there is no cancellation.
        while (!token.IsCancellationRequested)
        {
            // Wait before getting the next value.
            Thread.Sleep(1000);

            // Get the next value.
            double currentValue = cpuCounter.NextValue();

            if (currentValue > thisCPUPeak)
            {
                thisCPUPeak = currentValue;
            }

            // The action to perform.
            Action<double, double> a = (cv, p) => {
                CurrentCPUPeak.Text = cv.ToString();
                CPUPeak.Text = p.ToString();
            };

            startStopButton.Dispatcher.Invoke(a, 
                new object[] { currentValue, thisCPUPeak });
        }
    }, TaskCreationOptions.LongRunning);
}

以上注意事项:

现在,您还必须Task在类级别上保留 并引用 a CancellationTokenSource(产生CancellationToken):

private Task monitorTask = null;
private CancellationTokenSource cancellationTokenSource = null;

然后改变你的StartOrStop方法来调用任务

private void StartOrStop(object sender, EventArgs e)
{
    // If there is a task, then stop it.
    if (task != null)
    {
        // Dispose of the source when done.
        using (cancellationTokenSource)
        {
            // Cancel.
            cancellationTokenSource.Cancel();
        }

        // Set values to null.
        task = null;
        cancellationTokenSource = null;

        // Update UI.
        startStopButton.Content = "Resume";
    }
    else
    {
        // Update UI.
        startStopButton.Content = "Stop";

        // Create the cancellation token source, and
        // pass the token in when starting the task.
        cancellationTokenSource = new CancellationTokenSource();
        task = GetNextPeakAsync(cancellationTokenSource.Token);
    }
}

请注意,它不是检查标志,而是检查Task是否已经在运行;如果没有Task,则启动循环,否则,它使用CancellationTokenSource.

于 2012-08-08T18:47:35.850 回答
3

您没有看到 UI 更新,因为您没有正确使用性能计数器。第一次查询处理器时间性能计数器时,它始终为 0。请参阅此问题

cpuCounter = new PerformanceCounter("Processor", "% Processor Time", "_Total");

double currentValue = cpuCounter.NextValue();

Thread.Sleep(1000);

currentValue = cpuCounter.NextValue();

像这样简单的事情将解决问题,但您可能想要开发一个更强大的解决方案,考虑到上面评论中的一些评论。

于 2012-08-08T18:28:59.303 回答