1
votes

Instead of working in the background - this code still freeze my program:

private void button_Click(object sender, RoutedEventArgs e)
{
    this.Dispatcher.BeginInvoke(new Action(() =>
    {
        Thread.Sleep(5000);
        label.Content = "Done";
    }), DispatcherPriority.Normal);
}

I have tried with Thread/Tasks, thread example:

private void button_Click(object sender, RoutedEventArgs e)
{
    var t = new Thread(new ThreadStart(runtask));
    t.Start();
}

private void runtask()
{
    this.Dispatcher.BeginInvoke(new Action(() =>
    {
        Thread.Sleep(5000);
        label.Content = "Done";
    }), DispatcherPriority.Normal);
}

Task example:

private void button_Click(object sender, RoutedEventArgs e)
{
    Task.Run(() =>
    {
        Application.Current.Dispatcher.Invoke(DispatcherPriority.Background, new Action(() =>
        {
            Thread.Sleep(5000);
            label.Content = "Done";
        }));
    });
}

And still my program is freezing. Any suggestions?

2
What makes you think BeginInvoke runs it in the background? - mjwills
If you call Invoke, you are actually on main thread.. - sTrenat
@rhonin The documentation means it is asynchronous from the point of the view of the calling thread. It is definitely a synchronous operation from the point of view of the UI thread. - mjwills
Yes, exactly... - sTrenat

2 Answers

7
votes

From the documentation of the Dispatcher class:

Provides services for managing the queue of work items for a thread.

From the documentation of Dispatcher.BeginInvoke:

Executes the specified delegate asynchronously with the specified arguments on the thread that the Dispatcher was created on.

Here "asynchronously" refers to the secondary thread, not the main one. Because the main one is owned by the main Dispatcher. That means that every call of Invoke or BeginInvoke on that Dispatcher, from whatever Thread, will put the invoked Action in the queue of operations that the main Thread must execute, but from the point of view of the main Thread they will be executed synchronously, one after the other.

For example, if you put 3 Action like Thread.Sleep(1000); within 10 ms on the Dispatcher, whether with Invoke or BeginInvoke and from whether Thread, that Dispatcher will make the UI Thread to execute the 3 Action synchronously, so they will take a total of 3000 ms.

Maybe the documentation about BeginInvoke could have been written better, like:

Executes the specified delegate with the specified arguments on the thread that the Dispatcher was created on. The specified delegate is executed asynchronously from the point of view of the calling thread.

Now... Invoke or BeginInvoke?

Using Invoke, the secondary Thread is saying to the Dispatcher: let's execute this on the main Thread, and don't dare to return until your thread's job has finished. Then and only then I will continue.

For example, if you write this:

this.Dispatcher.Invoke(new Action(() =>
    {
        Thread.Sleep(5000);
        Debug.WriteLine("After Sleep");
    }));
Debug.WriteLine("Continuation on secondary Thread");

The Console will print after ~ 5000 ms:

"After Sleep"

"Continuation on secondary Thread"

Using BeginInvoke, instead, the Thread is saying: "hey, Dispatcher, queue this operation on the main Thread, but return as soon as possible so I can continue my job immediately".

In this case the Console will print immediately:

"Continuation on secondary Thread"

And after ~ 5000 ms:

"After Sleep"


Now, if your purpose is to execute some heavy operation on the background, you should learn about the async/await pattern, available from .NET 4.5 and C# 5.0.

In your example, I would write:

private async void button_Click(object sender, RoutedEventArgs e)
{
    await Task.Delay(5000); // await a heavy operation executed in background

    label.Content = "Done"; // control back to the UI Thread that executes this
}
-1
votes

You can use this small extension if your UI access is the last of your method.

https://mitsufu.wordpress.com/2015/08/03/dispatcher-switchto/

private void Button_Click(object sender, RoutedEventArgs e)
{
    Task.Run(async () =>
    {
        //heavy background operation
        await Dispatcher.SwitchTo();
        Title = "ok";
    });
}