C# 为什么我需要UI线程检查

C# 为什么我需要UI线程检查,c#,wpf,multithreading,user-interface,C#,Wpf,Multithreading,User Interface,要从其他线程更新UI,需要调用dispatcher的BeginInvoke方法。在调用方法之前,可以检查调用线程是否与调度程序关联 对于我的例子,我有两种方法来更新文本框;通过单击按钮和超时。守则: using System; using System.Timers; using System.Windows; using System.Windows.Controls; namespace WpfApplication1 { public partial class MainWind

要从其他线程更新UI,需要调用dispatcher的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();
                }));
            }
        }
    }
}
当我不选中Access()并且总是执行BeginInvoke时,一切都正常

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

所以我的问题是为什么不总是使用
BeginInvoke
并跳过
检查访问权限

如果需要调用(即,您正在接触另一个线程拥有的控件),那么在大多数情况下您都应该这样做。如果不需要调用,则应跳过这两个步骤

使用
CheckAccess
意味着您的代码不知道或不想假设它将在“正确”线程上运行。这有两个主要原因:通用性(您的代码在库中,您无法预测它将如何使用)和方便性(您只需要一种方法来处理这两种情况,或者您希望在不中断程序的情况下自由更改操作模式)

您的示例属于第二类:相同的方法为两种操作模式提供服务。在这种情况下,您有三种可能的选择:

  • 始终调用而不使用
    检查访问

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

  • 保持一切原样。

    由于用户界面和工作线程都调用了
    IncrementAndShowCounter
    ,因此使其适应情况可以让您继续处理其他问题。这是简单而好的;这也是编写库代码时所能做到的最好的方法(不允许任何假设)

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

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

  • 下面是第三个选项的示例代码:

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

    首先,没有太多的性能冲击。将委托添加到队列末尾将是一个非常快速的操作。此外,正如在另一个答案中提到的,您应该始终知道给定的代码块是从UI线程调用还是从非UI线程调用。您几乎不应该不知道某些代码是否正在UI线程中运行。几乎没有任何理由通过编程检查您是否在生产代码的UI线程中。@首先,我强烈建议任何人不要创建具有多个UI线程的应用程序。对于任何给定的问题,它几乎总是不恰当的解决方案。相反,应该使用单个UI线程。也就是说,即使有人处于您描述的情况下,他们仍然知道自己没有处于正确的线程中,并且需要调用另一个调度程序。不需要有条件地进行检查。@lll如果不能在UI线程外执行,则应异步执行。创建长时间阻止UI线程并防止其发送消息的代码几乎肯定是一个设计不当的解决方案,应该解决这个问题,而不是通过第二个UI线程。但是,正如我前面所说的,即使你选择使用第二个UI线程,你仍然知道你与你想要使用的线程处于不同的线程中,并且不需要通过编程进行检查。@Jon one几乎永远不会处于不知道代码是否在UI线程中运行的情况。设计良好的程序可以防止这种情况发生。在您真正不确定的异常情况下,总是调用而不检查实际上没有任何问题。你所描述的性能打击将是完全可以忽略不计的,特别是考虑到这是一种几乎永远不会发生的情况。这是对微优化的过早优化。@Jon问:“为什么不总是使用BeginInvoke并跳过CheckAccess?”回答:“因为与直接运行代码相比,使用BeginInvoke会降低性能。”在什么情况下,这并不是说性能是您在调用之前应该调用
    CheckAccess
    的原因?我根本不认为性能是一个考虑因素,性能差异只是会很小。也许开发人员认为应该有一个性能的冲击,而实际上没有,这就是为什么,但我看不出这实际上是一个有效的优化。