C# WPF Threading
我一直在遵循 WPF 线程模型指南来创建一个小应用程序,该应用程序将监视和显示当前和峰值 CPU 使用率。但是,当我在事件处理程序中更新我的当前 CPU 和峰值 CPU 时,我的窗口中的数字根本不会改变。调试时,我可以看到文本字段确实发生了变化,但没有在窗口中更新。
我听说构建这样的应用程序是不好的做法,应该改用 MVVM 方法。事实上,一些人对我能够在没有运行时异常的情况下运行它感到惊讶。无论如何,我想从第一个链接中找出代码示例/指南。
让我知道你的想法!
这是我的 xaml:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<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> |
这是我的代码
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
public partial class MainWindow : Window { public delegate void NextCPUPeakDelegate(); double thisCPUPeak = 0; private bool continueCalculating = false; PerformanceCounter cpuCounter; public MainWindow() : base() private void StartOrStop(object sender, EventArgs e) private void GetNextPeak() cpuCounter = new PerformanceCounter("Processor","% Processor Time","_Total"); double currentValue = cpuCounter.NextValue(); CurrentCPUPeak.Text = Convert.ToDouble(currentValue).ToString(); if (currentValue > thisCPUPeak) if (continueCalculating) |
这里有一些问题。首先,您正在主线程上工作,但您正在以一种非常迂回的方式进行工作。正是这里的这一行让您的代码具有响应性:
1
2 3 |
startStopButton.Dispatcher.BeginInvoke(
System.Windows.Threading.DispatcherPriority.SystemIdle, new NextCPUPeakDelegate(this.GetNextPeak)); |
来自
Executes the specified delegate asynchronously at the specified priority on the thread the Dispatcher is associated with.
即使您以很高的速率发送此消息,您也会在 UI 线程上排队工作,方法是让回帖回到消息循环中发生在后台线程上。这就是让您的 UI 完全响应的原因。
也就是说,你想像这样重组你的
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
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. // Get the next value. if (currentValue > thisCPUPeak) // The action to perform. startStopButton.Dispatcher.Invoke(a, |
以上注意事项:
-
根据 Dylan 的回答,对
PerformanceCounter 类上的NextValue 方法的调用必须有一段时间过期才能开始传递值。 -
CancellationToken 结构用于指示是否应该停止操作。这将驱动将在后台连续运行的循环。
-
您的方法现在返回一个代表背景信息的
Task 类。 -
不需要围绕
thisCPUPeak 值进行同步,因为单个后台线程是唯一可以读取和写入的地方;对Dispatcher 类上的Invoke 方法的调用具有传递给它的currentValue 和thisCPUPeak 值的副本。如果您想在任何其他线程(包括 UI 线程)中访问thisCPUPeak 值,则需要同步对该值的访问(很可能通过lock 语句)。
现在,您还必须在类级别上保留
1
2 |
private Task monitorTask = null;
private CancellationTokenSource cancellationTokenSource = null; |
然后改变你的
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
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. // Update UI. // Create the cancellation token source, and |
请注意,它不是检查标志,而是检查
取消现有循环
您没有看到 UI 更新,因为您没有正确使用性能计数器。第一次查询处理器时间性能计数器时,它将始终为 0。请参阅此问题。
1
2 3 4 5 6 7 |
cpuCounter = new PerformanceCounter("Processor","% Processor Time","_Total");
double currentValue = cpuCounter.NextValue(); Thread.Sleep(1000); currentValue = cpuCounter.NextValue(); |
这样简单的事情就可以解决问题,但您可能希望开发一个更强大的解决方案,同时考虑到上面评论中的一些评论。
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/tech/269557.html