简体   繁体   中英

Why do I need a UI Thread check

To update the UI from an other thread you need to call the BeginInvoke method of the dispatcher. Before you invoke you method you can check whether the calling thread is associated with the dispatcher.

For my example I have 2 ways to update a textbox; by clicking a button and by elapsing a timer. The Code:

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

When I don't CheckAccess() and just always execute the BeginInvoke everything works fine.

So my question is why not always use the BeginInvoke and skip the CheckAccess?

So my question is why not always use the BeginInvoke and skip the CheckAccess ?

That's exactly what you should do most of the time if invoking is required (ie you are touching a control owned by another thread). If invoking is not required then you should skip both of them.

Using CheckAccess implies that your code doesn't know or doesn't want to assume that it will run on the "correct" thread. There are two main reasons for this: genericity (your code is in a library and you can't predict how it will be used) and convenience (you want only one method to take care of both cases, or you want the freedom to change the mode of operation without breaking the program).

Your example falls in the second category: the same method services both modes of operation. In this case you have three possible options:

  1. Always invoke without CheckAccess .

    This is going to give you a performance hit (a negligible one here), and it will also make readers of the code assume that the method is only called from worker threads. Since the only benefit is that you will be writing a little less code, this is the worst option.

  2. Keep everything as it is.

    Since IncrementAndShowCounter is called from both UI and worker threads, making it adapt to the situation lets you move on to other problems. This is simple and good; it's also the best you can do when writing library code (no assumptions allowed).

  3. Never invoke from within the method, do it from outside as required.

    This is the best option on technical merit: since you know the context in which the method will be called, arrange for the invocation to happen outside it. This way the method is not tied to any specific thread and you don't get unnecessary performance penalties.

Here's example code for the third option:

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

myButton.Click += (_, __) => IncrementAndShowCounter();
timer.Elapsed += (_, __) => Dispatcher.BeginInvoke(IncrementAndShowCounter);

If you 100% sure that the calling thread is UI thread - you can use the "DO" method directly.

If you 100% sure that the calling thread is not UI thread, but the operation should be done on the UI thread, you just call the 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();
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM