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) |
- 单击”开始”后,您的 UI 是否仍然响应? (按钮是否变为停止,您可以单击它吗?)。尝试阅读代码时,您似乎会在 UI 线程上一遍又一遍地调用 GetNextPeak() ,而不是在后台线程上,所以我认为整个 UI 都会被锁定。不过我可能是错的。我还没有尝试将代码复制/粘贴到 WPF 项目中。
- 不完全确定您在寻找什么建议,但您的代表每秒循环超过一千次。它的运行速度与 CPU 可以处理的一样快,这意味着它会像 FPS 游戏一样吞噬 CPU 周期——或者更糟。另外我应该提到性能计数器对我的读数不高于 0.0(不准确)。
- @rally25rs 是的,我的 UI 响应非常好……按钮文本确实发生了变化。 @Erode 似乎没有锁定或任何东西,除了当我再次循环时,我将优先级设置为 SystemIdle
这里有一些问题。首先,您正在主线程上工作,但您正在以一种非常迂回的方式进行工作。正是这里的这一行让您的代码具有响应性:
1
2 3 |
startStopButton.Dispatcher.BeginInvoke(
System.Windows.Threading.DispatcherPriority.SystemIdle, new NextCPUPeakDelegate(this.GetNextPeak)); |
来自 BeginInvoke 方法文档(重点是我的):
Executes the specified delegate asynchronously at the specified priority on the thread the Dispatcher is associated with.
即使您以很高的速率发送此消息,您也会在 UI 线程上排队工作,方法是让回帖回到消息循环中发生在后台线程上。这就是让您的 UI 完全响应的原因。
也就是说,你想像这样重组你的 GetNextPeak 方法:
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 语句)。
现在,您还必须在类级别上保留 Task 并引用 CancellationTokenSource(生成 CancellationToken):
1
2 |
private Task monitorTask = null;
private CancellationTokenSource cancellationTokenSource = null; |
然后改变你的 StartOrStop 方法来调用任务
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 |
请注意,它不是检查标志,而是检查 Task 是否已经在运行;如果没有 Task 则启动循环,否则,使用 CancellationTokenSource.
取消现有循环
您没有看到 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(); |
这样简单的事情就可以解决问题,但您可能希望开发一个更强大的解决方案,同时考虑到上面评论中的一些评论。
- 这让它”工作”了,尽管现在它只会读取 0 或 100 的 CPU 使用率值。我在 GetNextPeak() 中添加了 System.Threading.Thread.Sleep(100); 以稍微减慢它的速度。
- @Tory正如我链接的问题中所建议的那样,在查询到性能计数器之间必须经过一段时间才能获得准确的值。在查询之间添加睡眠,它应该可以解决您的问题。
来源:https://www.codenong.com/11870298/