4

I have the doubt regarding when to use the Dispatcher.Invoke to update something on UI from different Thread.

Here's my code...

public Window4()
    {
        InitializeComponent();
        this.DataContext = this;

      Task.Factory.StartNew(() => Test() );
    }

    private List<string> listOfString = new List<string>();

    public List<string> ListOfString
    {
        get { return listOfString; }
        set { listOfString = value; }
    }

    public void Test()
    {
        listOfString.Add("abc");
        listOfString.Add("abc");
        listOfString.Add("abc");
    }

 <Grid>
    <ListView ItemsSource="{Binding ListOfString}" />
</Grid>

I am starting a new Task on the different Thread, do i need to use Dispatcher.BeginInvoke to update the UI.

In this case it is updating the UI, but i've seen some scenarios where people update UI using Dispatcher.Invoke or BeginInvoke from the different Thread.

So my question is when we have to do that and why in this case it is working fine.

Thanks & Regards, BHavik

Learner
  • 1,490
  • 2
  • 22
  • 35

2 Answers2

8

I have the doubt regarding when to use the Dispatcher.Invoke to update something on UI from different Thread.

When you are on a different thread you will always have to use the dispatcher to update a ui component that belongs to another thread.

I am starting a new Task on the different Thread, do i need to use Dispatcher.BeginInvoke to update the UI.

Tasks allow for multiple operations to be performed without blocking the thread they are called from but that doesn't mean they are on a different thread. However when updating the UI from inside a Task you will need to use the dispatcher.

In this case it is updating the UI, but i've seen some scenarios where people update UI using Dispatcher.Invoke or BeginInvoke from the different Thread.

Invoke will block the calling thread while it is performing the action and BeginInvoke will not. BeginInvoke will return control immediately to the caller, Invoke may cause the calling thread to hang if it is performing a heavy operation.

This is from msdn documentation,

In WPF, only the thread that created a DispatcherObject may access that object. For example, a background thread that is spun off from the main UI thread cannot update the contents of a Button that was created on the UI thread. In order for the background thread to access the Content property of the Button, the background thread must delegate the work to the Dispatcher associated with the UI thread. This is accomplished by using either Invoke or BeginInvoke. Invoke is synchronous and BeginInvoke is asynchronous.

Edit: In response to your comment I ran some tests.

When calling Test() from a task (without using the dispatcher) I got this error "The calling thread cannot access this object because a different thread owns it."

So I created a method called PrintThreadID(). I printed the thread before entering the task then from inside the task and it does report both are running on the same thread ID.

The error is misleading because it says the calling thread is different than the one that owns it which the PrintThreadID() function shows is not true, they are in fact on the same thread. Tasks while on the same thread still cannot update a UI component without using Dispather.Invoke().

So here is a working example which will update the Grid from a task.


public partial class MainWindow : Window
{
    public List<string> myList { get; private set; }

    public MainWindow()
    {
        InitializeComponent();
        myList = new List<string>();
        label1.Content = Thread.CurrentThread.ManagedThreadId.ToString();

        Task.Factory.StartNew(PrintThreadID);
        Task.Factory.StartNew(Test);

    }

    private void PrintThreadID()
    {
        label1.Dispatcher.Invoke(new Action(() =>
            label1.Content += "..." + Thread.CurrentThread.ManagedThreadId.ToString()));
    }

    private void Test()
    {
        myList.Add("abc");
        myList.Add("abc");
        myList.Add("abc");

        // if you do not use the dispatcher you will get the error "The calling thread cannot access this object because a different thread owns it."


        dataGrid1.Dispatcher.Invoke(new Action(() =>
        {
            dataGrid1.ItemsSource = myList.Select(i => new { Item = i });
        }));
    }
}
Despertar
  • 21,627
  • 11
  • 81
  • 79
  • 1
    But if i am updating or adding in the list that is bound to the UI , do i still need to use the Dispatcher's Invoke or BeginInvoke... – Learner Apr 02 '12 at 16:47
  • Depends on the list type. `List`: no, but the updates won't appear in the UI anyway. `ObservableCollection`: yes. – Paul Stovell Apr 02 '12 at 16:53
  • When updating it from inside a task, yes you have to call Dispatcher.Invoke(), I posted the full example of my test to show this above in my answer. – Despertar Apr 02 '12 at 17:23
6

Your test isn't valid as it isn't actually updating your UI. If you want proof, add this sleep call:

public void Test()
{
    Thread.Sleep(10000);
    listOfString.Add("abc");
    listOfString.Add("abc");
    listOfString.Add("abc");
}

You'll find that your UI appears and the list is empty. 10 seconds, 30 seconds, 3 months later, the list won't contain your strings.

Instead your test is demonstrating a race condition - your Test() method is completing fast enough that the strings are added to the list before the UI appears on screen and reads the list.

To fix it, change your collection to an ObservableCollection<string>. But then you'll encounter the next problem - you can't update an ObservableCollection on a background thread. So that's where the Dispatcher comes in.

Paul Stovell
  • 32,377
  • 16
  • 80
  • 108
  • do not quite understand your answer. The UI thread would simply sleep for 10seconds and the item "abc" will be added. – KMC Jul 15 '17 at 07:25