Showing posts with label Invoke. Show all posts
Showing posts with label Invoke. Show all posts

Tuesday, December 7, 2010

WPF - Dispatcher.BeginInvoke and Closures‏

In one of my previous posts I discussed about the issues with using Dispatcher.Invoke. In this post, we are discussing some possible issues with Dispatcher.BeginInvoke and Closures. Let us start with an example and see the expected result. Then we will be discussing how we can improve the results.

<Window x:Class="CapturedVariableLambda_BeginInvoke.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="275" Width="476">
<Grid>
<TextBox Height="27" HorizontalAlignment="Left" Margin="64,32,0,0"
Name="myTextBox" VerticalAlignment="Top" Width="358" />
<Button Content="Button" Height="51" HorizontalAlignment="Left"
Margin="133,94,0,0" Name="myButton" VerticalAlignment="Top"Width="196" Click="myButton_Click" />
</Grid>
</Window>

Let us update the code behind with the definition of myButton_Click handler.

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

private void myButton_Click(object sender, RoutedEventArgs e)
{
Thread t = new Thread(() =>
{
for (int i = 0; i <= 9; i++)
{
myTextBox.Dispatcher.BeginInvoke(
new Action(() => myTextBox.Text =
string.Format("{0}{1}", myTextBox.Text, i.ToString())));
}
});

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

You probably are imagining the textbox to be updated as follows when we click the button.


But as we click the button, the text box is updated as follow:


Hmm! It's interesting, but why? Wanna know? Let us discuss!

Basically this is a known behavior of Captured variables. They are outer variables used by lambda expression / statement. These types of lambda expressions / statements are called Closures. As we all know there is delayed execution of lambda expression. While being executed, they use the current value of the captured variable rather than the value when they were created.

In order to solve this problem of closure, there is a universal remedy. Whenever we need to capture a variable in a closure, we must be making a local copy of the variable by declaring a local variable in lambda and assigning the value of captured variable to this local variable.

Let us update our code based on above explanation and see if that results in expected result. Let us update the handler for button click event as follows:

private void myButton_Click(object sender, RoutedEventArgs e)
{
Thread t = new Thread(() =>
{
for (int i = 0; i <= 9; i++)
{
int iLocal = i;
myTextBox.Dispatcher.BeginInvoke(
new Action(() => myTextBox.Text =
string.Format("{0}{1}", myTextBox.Text, iLocal.ToString())));
}
});

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

Now we run the application and click the button. The form updates as follows.


As you can see this is fixed now as per expectation.

Finally a word of advice: Always assign a captured variable to a local variable in Closure.

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.