为什么我需要一个UI线程检查

本文关键字:一个 UI 线程 检查 为什么 | 更新日期: 2023-09-27 18:02:15

要从其他线程更新UI,需要调用分派器的BeginInvoke方法。在调用方法之前,您可以检查调用线程是否与调度程序相关联。

对于我的例子,我有两种方法来更新文本框;通过点击一个按钮并运行一个计时器。代码:

using System;
using System.Timers;
using System.Windows;
using System.Windows.Controls;
namespace WpfApplication1
{
    public partial class MainWindow : Window
    {
        private int i = 0;
        private TextBlock myText = new TextBlock();
        private Button myButton = new Button();
        private Timer timer = new Timer(2 * 1000);
        private StackPanel panel = new StackPanel();
        public MainWindow()
        {
            InitializeComponent();
            myButton.Content = "Click";
            panel.Children.Add(myText);
            panel.Children.Add(myButton);
            this.AddChild(panel);
            myButton.Click += (_, __) => IncrementAndShowCounter();
            timer.Elapsed += (_, __) => IncrementAndShowCounter();
            timer.Start();
        }
        private void IncrementAndShowCounter()
        {
            i++;
            if (this.Dispatcher.CheckAccess())
            {
                myText.Text = i.ToString();
            }
            else
            {
                this.Dispatcher.BeginInvoke((Action)(() =>
                {
                    myText.Text = i.ToString();
                }));
            }
        }
    }
}

当我不CheckAccess(),只是总是执行BeginInvoke一切工作正常。

所以我的问题是为什么不总是使用BeginInvoke和跳过CheckAccess?

为什么我需要一个UI线程检查

所以我的问题是为什么不总是使用BeginInvoke而跳过CheckAccess ?

这正是你应该做的大部分时间,如果调用是必需的(即你正在触摸由另一个线程拥有的控件)。如果不需要调用,则应该跳过它们。

使用CheckAccess意味着你的代码不知道或不想假设它将在"正确"的线程上运行。这样做有两个主要原因:泛型(你的代码在一个库中,你不能预测它将如何被使用)和便利性(你只需要一个方法来处理这两种情况,或者你想在不破坏程序的情况下自由地改变操作模式)。

您的示例属于第二类:相同的方法服务于两种操作模式。在本例中,您有三个可能的选项:

  1. 始终不调用CheckAccess

    这将给您带来性能上的影响(在这里可以忽略不计),并且还会使代码的读者认为该方法只能从工作线程调用。由于唯一的好处是您将编写更少的代码,因此这是最糟糕的选择。

  2. 使一切。

    由于IncrementAndShowCounter是由UI和工作线程调用的,使其适应这种情况可以让您继续处理其他问题。这既简单又好;这也是你在编写库代码时所能做的最好的事情(不允许有任何假设)。

  3. 永远不要从方法内部调用,根据需要从外部调用

    从技术上来说,这是最好的选择:因为您知道将在其中调用方法的上下文,所以可以安排调用在上下文之外进行。这样,该方法就不会绑定到任何特定的线程,并且您不会得到不必要的性能损失。

下面是第三个选项的示例代码:
private void IncrementAndShowCounter()
{
    i++;
    myText.Text = i.ToString();
}
myButton.Click += (_, __) => IncrementAndShowCounter();
timer.Elapsed += (_, __) => Dispatcher.BeginInvoke(IncrementAndShowCounter);

如果你100%确定调用线程是UI线程-你可以直接使用"DO"方法。

如果你100%确定调用线程不是UI线程,但操作应该在UI线程上完成,你只需调用BeginInvoke

....
// 100% I'm sure the Click handler will be invoked on UI thread
myButton.Click += (_, __) => IncrementAndShowCounter();
// here I'm not sure
timer.Elapsed += (_, __) => Dispatcher.BeginInvoke(IncrementAndShowCounter); 
// 100%  not UI thred here:
Task.Factory.StartNew(() => Dispatcher.BeginInvoke(IncrementAndShowCounter), TaskScheduler.Default)

private void IncrementAndShowCounter()
{
    i++;
    myText.Text = i.ToString();
}