I’m working on a Mahjong game that adds 144 image-holding usercontrols to a canvas. I do this in a loop and during the loop, my user interface freezes. Not even the command progressbar.Visibility = System.Windows.Visibility.Visible;
is executed until the operation finishes. Isn’t that weird?
When this code is being executed:
private void ButtonSync_Click(object sender, RoutedEventArgs e)
{
progressbar.Visibility = System.Windows.Visibility.Visible;
canvas.Children.Clear();
for (int i = 0; i < 30; i++)
{
for (int j = 0; j < 40; j++)
{
var star=new Star();
star.SetValue(Canvas.LeftProperty, i * 15d);
star.SetValue(Canvas.TopProperty, j * 15d);
canvas.Children.Add(star);
}
}
progressbar.Visibility = System.Windows.Visibility.Collapsed;
}
This is the response:
When the loop is finished, all the stars are on my screen, but the user had no idea what happened. We need a way to execute the code in a different thread, so the main thread can show the progress bar. Enter the background worker.
.NET provides the very handy Background Worker Class to execute code in a different thread. We will start by creating a new Background Worker. The DoWork event is called by the RunWorkerAsync method and the RunWorkerCompleted event is being fired when the operation has been completed.
private void ButtonAsync_Click(object sender, RoutedEventArgs e)
{
var bw = new BackgroundWorker();
bw.DoWork += (s, a) =>
{
for (int i = 0; i < 30; i++)
{
for (int j = 0; j < 40; j++)
{
Dispatch(i, j);
}
}
};
bw.RunWorkerCompleted += (s, a) =>
{
progressbar.Visibility = System.Windows.Visibility.Collapsed;
};
progressbar.Visibility = System.Windows.Visibility.Visible;
canvas.Children.Clear();
bw.RunWorkerAsync();
}
We cannot access the Canvas from the background worker so we use Dispatcher.BeginInvoke
. In my case the Dispatcher.BeginInvoke
must be in a different method outside the DoWork
delegate. If I place it inside the delegate all stars will be at the same position because every i and j will have the last value from the for-loop.
private void Dispatch(int i, int j)
{
Dispatcher.BeginInvoke(() =>
{
var star = new Star();
star.SetValue(Canvas.LeftProperty, i * 15d);
star.SetValue(Canvas.TopProperty, j * 15d);
canvas.Children.Add(star);
});
}
If you do not use the ZDispatcher.BeginInvokeZ but add the user controls directly to the canvas, the compiler will not complain but at run time you will get the error:
Invalid cross-thread access.
I hope this simple example shows you how you can use the Background worker to delegate time-consuming processes to different threads and inform the UI when the operation has been completed.