草庐IT

C# WPF 线程

codeneng 2023-03-28 原文

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()
    {
        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));
        }
    }
}

  • 单击"开始"后,您的 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.
        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);
}

以上注意事项:

  • 根据 Dylan 的回答,对 PerformanceCounter 类上的 NextValue 方法的调用必须有一段时间过期才能开始传递值。

  • CancellationToken 结构用于指示是否应该停止操作。这将驱动将在后台连续运行的循环。

  • 您的方法现在返回一个代表背景信息的 Task 类。

  • 不需要围绕 thisCPUPeak 值进行同步,因为单个后台线程是唯一可以读取和写入的地方;对 Dispatcher 类上的 Invoke 方法的调用具有传递给它的 currentValuethisCPUPeak 值的副本。如果您想在任何其他线程(包括 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.
        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.

取消现有循环


您没有看到 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正如我链接的问题中所建议的那样,在查询到性能计数器之间必须经过一段时间才能获得准确的值。在查询之间添加睡眠,它应该可以解决您的问题。

有关C# WPF 线程的更多相关文章

  1. ruby - RuntimeError(自动加载常量 Apps 多线程时检测到循环依赖 - 2

    我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("

  2. ruby - 如何让Ruby捕获线程中的语法错误 - 2

    我正在尝试使用ruby​​编写一个双线程客户端,一个线程从套接字读取数据并将其打印出来,另一个线程读取本地数据并将其发送到远程服务器。我发现的问题是Ruby似乎无法捕获线程内的错误,这是一个示例:#!/usr/bin/rubyThread.new{loop{$stdout.puts"hi"abc.putsefsleep1}}loop{sleep1}显然,如果我在线程外键入abc.putsef,代码将永远不会运行,因为Ruby将报告“undefinedvariableabc”。但是,如果它在一个线程内,则没有错误报告。我的问题是,如何让Ruby捕获这样的错误?或者至少,报告线程中的错误?

  3. ruby - 如何在 ruby​​ 中运行后台线程? - 2

    我是ruby​​的新手,我认为重新构建一个我用C#编写的简单聊天程序是个好主意。我正在使用Ruby2.0.0MRI(Matz的Ruby实现)。问题是我想在服务器运行时为简单的服务器命令提供I/O。这是从示例中获取的服务器。我添加了使用gets()获取输入的命令方法。我希望此方法在后台作为线程运行,但该线程正在阻塞另一个线程。require'socket'#Getsocketsfromstdlibserver=TCPServer.open(2000)#Sockettolistenonport2000defcommandsx=1whilex==1exitProgram=gets.chomp

  4. ruby - Rails 开发服务器、PDFKit 和多线程 - 2

    我有一个使用PDFKit呈现网页的pdf版本的Rails应用程序。我使用Thin作为开发服务器。问题是当我处于开发模式时。当我使用“bundleexecrailss”启动我的服务器并尝试呈现任何PDF时,整个过程会陷入僵局,因为当您呈现PDF时,会向服务器请求一些额外的资源,如图像和css,看起来只有一个线程.如何配置Rails开发服务器以运行多个工作线程?非常感谢。 最佳答案 我找到的最简单的解决方案是unicorn.geminstallunicorn创建一个unicorn.conf:worker_processes3然后使用它:

  5. ruby - Ruby 1.9.1 中的 native 线程,对我有什么好处? - 2

    所以,Ruby1.9.1现在是declaredstable.Rails应该与它一起工作,并且正在慢慢地将gem移植到它。它具有native线程和全局解释器锁(GIL)。自从GIL到位后,原生线程是否比1.9.1中的绿色线程有任何优势? 最佳答案 1.9中的线程是原生的,但它们被“放慢了速度”,一次只允许一个线程运行。这是因为如果线程真的并行运行,它会混淆现有代码。优点:IO现在在线程中是异步的。如果一个线程阻塞在IO上,那么另一个线程将继续执行直到IO完成。C扩展可以使用真正的线程。缺点:任何非线程安全的C扩展都可能存在使用Thre

  6. ruby - 使写入文件线程安全 - 2

    我在一个ruby​​文件中有一个函数可以像这样写入一个文件File.open("myfile",'a'){|f|f.puts("#{sometext}")}这个函数在不同的线程中被调用,使得像上面这样的文件写入不是线程安全的。有谁知道如何以最简单的方式使这个文件写入线程安全?更多信息:如果重要的话,我正在使用rspec框架。 最佳答案 您可以通过File#flock给锁File.open("myfile",'a'){|f|f.flock(File::LOCK_EX)f.puts("#{sometext}")}

  7. Ruby 线程与 Watir - 2

    我编写了几个类来控制我想如何处理多个网站,两者都使用类似的方法(即登录、刷新)。每个类都打开自己的WATIR浏览器实例。classSite1definitialize@ie=Watir::Browser.newenddeflogin@ie.goto"www.blah.com"endend无线程的main中的代码示例如下require'watir'require_relative'site1'agents=[]agents这工作正常,但在当前代理完成登录之前不会移动到下一个代理。我想合并多线程来处理这个问题,但似乎无法让它工作。require'watir'require_relative

  8. ruby - 在多个线程中引用类方法会导致自动加载循环依赖崩溃 - 2

    代码:threads=[]Thread.abort_on_exception=truebegin#throwexceptionsinthreadssowecanseethemthreadseputs"EXCEPTION:#{e.inspect}"puts"MESSAGE:#{e.message}"end崩溃:.rvm/gems/ruby-2.1.3@req/gems/activesupport-4.1.5/lib/active_support/dependencies.rb:478:inload_missing_constant':自动加载常量MyClass时检测到循环依赖稍加研究后,

  9. Ruby 多线程/多处理读物 - 2

    任何人都可以推荐任何详细介绍Ruby多线程/多处理的复杂性的好的多线程/处理书籍/网站吗?我尝试使用ruby​​线程,基本上在1.9vm上的无死锁代码中它在jruby中遇到了死锁。是的,我意识到差异很大(jruby没有GIL),但我想知道是否有用于ruby​​中多线程编程的策略或类集,我只需要继续阅读。旁注:从java到ruby​​必须定义是否需要重新输入锁,这有点奇怪。 最佳答案 如果你使用Ruby1.9,你可以试试Fiber,它是Ruby中线程的一大改进http://ruby-doc.org/core-1.9/classes/F

  10. ruby - 跨线程共享枚举器 - 2

    我想从不同线程调用一个公共(public)枚举器。当我执行以下操作时,enum=(0..1000).to_enumt1=Thread.newdopenum.nextsleep(1)endt2=Thread.newdopenum.nextsleep(1)endt1.joint2.join它引发了一个错误:Fibercalledacrossthreads.当enum在从t1调用一次后从t2调用时。为什么Ruby设计为不允许跨线程调用枚举器(或纤程),以及是否有其他方法可以提供类似的功能?我猜测枚举器/纤程上的操作的原子性在这里是相关的,但我不完全确定。如果这是问题所在,那么在使用时独占锁定

随机推荐