为什么我需要UI线程检查

Ric*_*eft 0 c# wpf user-interface multithreading

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

对于我的例子,我有2种方法来更新文本框; 通过单击按钮并通过计时器.代码:

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();
                }));
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

当我没有CheckAccess()并且总是执行BeginInvoke时,一切正常.

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

Jon*_*Jon 5

所以我的问题是为什么不总是使用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);
Run Code Online (Sandbox Code Playgroud)

  • 首先,实际上并没有太大的性能影响.将一个委托添加到队列的末尾将是一个非常快速的操作.另外,正如在另一个答案中提到的,你应该*总是*知道是否将从UI线程或非UI线程调用给定的代码块.你几乎不应该知道某些代码是否在UI线程中运行.几乎没有任何理由以编程方式检查您是否在生产代码中的UI线程中. (2认同)
  • @Jon One几乎不会出现不知道代码是否在UI线程中运行的情况.精心设计的程序可以防止这种情况发生.在你真正不确定的异常情况下,*总是*调用没有任何问题,没有检查.您描述的性能损失将完全可以忽略不计,特别是考虑到这种情况几乎不会发生.这是微优化的过早优化. (2认同)
  • @Jon问题:"为什么不总是使用BeginInvoke并跳过CheckAccess?" 答:"因为与直接运行代码相比,使用BeginInvoke时性能会受到影响." 以什么方式*不*说性能是为什么你应该在调用之前调用`CheckAccess`?我认为性能根本不是考虑因素,性能差异只会很小.也许开发人员想象当真的没有时应该会有性能损失,这就是为什么,但我看不出这个*实际上*是一个有效的优化. (2认同)