为什么我需要UI线程检查

时间:2014-05-07 14:36:38

标签: c# wpf multithreading user-interface

要从其他线程更新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();
                }));
            }
        }
    }
}

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

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

2 个答案:

答案 0 :(得分:5)

  

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

如果需要调用,那么你应该在大多数情况下做什么(即你正在触摸另一个线程所拥有的控件)。如果不需要调用,那么你应该跳过它们。

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

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

  1. 始终在没有CheckAccess的情况下调用。

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

  2. 保持原样。

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

  3. 永远不要在方法中调用,根据需要从外部执行。

    这是技术优点的最佳选择:因为您知道调用该方法的上下文,所以安排调用发生在它之外。这样,该方法不依赖于任何特定的线程,并且您不会受到不必要的性能损失。

  4. 以下是第三个选项的示例代码:

    private void IncrementAndShowCounter()
    {
        i++;
        myText.Text = i.ToString();
    }
    
    myButton.Click += (_, __) => IncrementAndShowCounter();
    timer.Elapsed += (_, __) => Dispatcher.BeginInvoke(IncrementAndShowCounter);
    

答案 1 :(得分:2)

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