C#WPF线程

时间:2012-08-08 17:52:49

标签: c# wpf multithreading

我一直在关注WPF线程模型的this指南,以创建一个监视和显示当前和峰值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));
        }
    }
}

2 个答案:

答案 0 :(得分:4)

这里有一些问题。首先,你正在主线程上工作,但你是以非常环形方式进行的。这就是这一行,它允许您的代码响应:

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

来自BeginInvoke method documentation(强调我的):

  

在与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,并引用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取消现有循环。

答案 1 :(得分:3)

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

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

double currentValue = cpuCounter.NextValue();

Thread.Sleep(1000);

currentValue = cpuCounter.NextValue();

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