Monday, November 29, 2010

WPF - Dispatcher.Invoke is an overkill

In this post we discuss that executing a synchronous operation on UI threads can be an overkill. It should only be used with care. As we know, we can use Invoke and BeginInvoke on Dispatcher to execute a synchronous and asynchronous operation on UI thread respectively. Calling Invoke is an overkill.

In order to discuss this point. Let us use both, Invoke and BeginInvoke, in a sample WPF application. The following is a WPF window with two text boxes and a button.

<Window x:Class="WpfDispatcher_OperationInvocation.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="451">
<Grid>
<TextBox Height="30" Margin="175,24,30,0" Name="textBoxInvoke" VerticalAlignment="Top" />
<TextBox Height="30" Margin="175,79,30,0" Name="textBoxBeginInvoke" VerticalAlignment="Top" />
<Label Margin="14,24,0,0" Name="label1" Height="24" HorizontalAlignment="Left"
VerticalAlignment="Top" Width="133">Updated Through Invoke</Label>
<Label Height="32" HorizontalAlignment="Left" Margin="9,77,0,0" Name="label2"
VerticalAlignment="Top" Width="165">Updated Through Begin Invoke</Label>
<Button Height="32" Margin="128,0,146,82" Name="button1" VerticalAlignment="Bottom"
Click="button1_Click" >Update Text</Button>
</Grid>
</Window>

As you can see above, we have two textboxes and a button. We have also specified button1_Click as the Click handler for button1. We are using Invoke for one operation on UI thread and BeginInvoke for other. In both these operations, we are updating the text of one of the text boxes.

public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}

void button1_Click(object sender, RoutedEventArgs e)
{
Thread t = new Thread(
() =>
{
textBoxInvoke.Dispatcher.Invoke(
new Action(() => textBoxInvoke.Text = "Updated"));

textBoxBeginInvoke.Dispatcher.BeginInvoke(
new Action(() => textBoxBeginInvoke.Text = "Updated"));
});

t.IsBackground = true;
t.Start();
}
}

Whenever we call Invoke or BeginInvoke on a Dispatcher, we are introducing a new work item in the dispatcher queue of the dispatcher. So what is the difference between the two? The answer is that Invoke is a synchronous operation and BeginInvoke is an asynchronous operation.

When we call BeginInvoke on a Dispatcher, it pushes the work item on the queue and returns (1 in above figure) DispatcherOperation which can be used to keep on eye on the status of the execution of the delegate. The calling thread wouldn't wait for anything and goes on any other tasks (2 in above figure). On the other side, Invoke causes the work item to be pushed on the queue. It is pushed based on its DispatcherPriority. Now the calling thread is waiting for this operation to be completed. The work item keeps waiting on the Dispatcher queue when there comes her turn of execution by Dispatcher. The dispatcher executes it. Now the result is returned to the calling thread. There might be many work items in the dispatcher queue with same or greater priority values. Calling an operation like this is lethal for the calling thread cause it could be waiting for a long time.


But there might be certainly situations when you can not go without going synchronous. In the following example, we need the text entered in a text box and show it in a MessageBox.

string enteredText = "";

textBox1.Dispatcher.Invoke(new Action(() => enteredText = textBox1.Text), null);
MessageBox.Show(enteredText);

If we don't call this synchronously then we by the time we reach MessageBox.Show this item hasn't been executed by textBox1's Dispatcher so the text is not yet copied to winTitle so would be showing an empty MessageBox.

We can still wait on work item introduced in Dispatcher using BeginInvoke. Basically BeginInvoke does not have a corresponding “EndInvoke”. Many a times we need to find out when an operation ended before starting another dependent work item. BeginInvoke returns a DispatcherOperation. We can use Status property of DispatcherOperation to find this out. If it is Completed then the dispatcher is done executing this operation and we can go ahead with executing the dependent work item. We can also call Wait() on DispatcherOperation which makes this operation as synchronous which is similar to calling Invoke on Dispatcher. We need to be careful as this can lead to the same issue of deadlock as Invoke when UI and worker thread are waiting for some resource which one is needing and the other has it. DispatcherOperation also supports aborting a work item. When we abort it then it is removed from dispatcher’s queue. It’s state is set as Aborted.
DispatcherOperation dispatcherOperation = 
textBoxBeginInvoke.Dispatcher.BeginInvoke(
new Action(() => textBoxBeginInvoke.Text = "Updated"));

//Any other Task(s)

if (dispatcherOperation.Status != DispatcherOperationStatus.Completed)
{
dispatcherOperation.Wait();
}


Now the last words! Avoid using Invoke on Dispatcher when we are absolutely not sure that we need it, otherwise, use BeginInvoke if it wouldn't be making any difference.

1 comment:

Syed said...

Nice explanation, thanks