我一直在关注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));
}
}
}
答案 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);
}
关于上述事项的说明:
根据NextValue
method,PerformanceCounter
class上Dylan's answer的来电必须在价值开始前有一段时间到期。
CancellationToken structure用于指示是否应停止操作。这将驱动将在后台持续运行的循环。
您的方法现在返回Task
class,代表背景信息。
围绕thisCPUPeak
值的同步不是,因为单个后台线程是唯一的位置,在那里读取和写入;对Invoke
method上的Dispatcher
class的调用包含传递给它的currentValue
和thisCPUPeak
值的副本。如果要访问任何其他线程(包括UI线程)中的thisCPUPeak
值,则需要同步对值的访问(最有可能通过lock
statement)。< / p>
现在,您还必须在类级别上保留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();
这样简单的事情可以解决问题,但你可能想要开发一个更强大的解决方案,同时考虑上面评论中的一些评论。