WP7: Add User Controls (graphics) in background thread

When time-consuming operations are executed on the Main Thread on WP7, the user interface can stop responding to user input and even freeze so it won’t update bindings, visual states or progress bars.

Time-consuming operations

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:

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.

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.