Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dispatcher.Dispatch on the UI thread

Tags:

c#

wpf

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

like image 263
Learner Avatar asked Dec 12 '22 03:12

Learner


1 Answers

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 });
        }));
    }
}
like image 131
Despertar Avatar answered Dec 27 '22 10:12

Despertar