Adding background threads to Silverlight applications

Background worker threads are implemented in Silverlight applications using the BackgroundWorker class, which is part of the System.ComponentModel namespace. The BackgroundWorker implements threads that are started, run to completion, and then self-terminate. The UI thread in the Silverlight application is only responsible for starting the BackgroundWorker thread.

BackgroundWorker threads are basically the same as a .NET System.Windows.

■■ Thread, except that it includes additional event handlers to handle progress update and completion events.

Creating a BackgroundWorker thread

To create a BackgroundWorker thread, you need to create an instance of the class. You also need to specify the DoWork event handler of the BackgroundWorker object. The BackgroundWorker event handler is a DoWorkEvent handler that points to a Silverlight function that is called when the thread is started. For example:

BackgroundWorker worker = new BackgroundWorker(); worker.DoWork +=new DoWorkEventHandler(DoBackground);

Starting a BackgroundWorker thread

BackgroundWorker threads are started by calling the RunWorkerAsync() function of the object. The RunWorkerAsync() function creates a new thread that begins executing the Silverlight function attached to the DoWork event handler of the object.

BackgroundWorker threads support the ability to pass arguments to the DoWork event handler. This is done by passing one object as an argument to the RunWorkerAsync() function. For example:

worker.RunWorkerAsync(someNum);

The RunWorkerAsync() function supports only zero or one argument. The argument is passed to the background as the Argument property of the DoWorkEventArgs object. For example, the following code accesses the argument from the DoWork event handler:

void DoBackground(object sender, DoWorkEventArgs e) {

int num = Convert.ToInt32(e.Argument);

Accessing the UI from a BackgroundWorker thread

Silverlight requires that UI objects be updated on a UI thread. The BackgroundWorker thread is not a UI thread and therefore cannot directly access the UI objects. To access UI objects from the BackgroundWorker thread, you need to implement a delegate method that can be invoked by the BackgroundWorker thread to perform work on the UI thread on its behalf.

To implement a delegate function, first define the delegate as a member of the Silverlight application class. For example:

delegate void UpdateUIDelegate(int num, String str);

Then, inside the worker thread, you can create an instance of the delegate function that points to a function in the Silverlight application that can update the UI. For example, the following code creates an instance of the UpdateUIDelegate() function, defined previously, that points to a Silverlight function named UpdateUI() :

UpdateUIDelegate doUpdate = new UpdateUIDelegate(UpdateUi);

The Silverlight function can then be called using the Dispatcher.BeginInvoke() function of the Silverlight application class. The BeginInvoke() function accepts the delegate function as the first parameter. If there are arguments that need to be passed to the Silverlight function they are passed as a params object[] following the delegate. For example, the following code invokes the delegate object doUpdate and passes two arguments to the UpdateUI() Silverlight function:

this.Dispatcher.BeginInvoke(doUpdate, num, str);

The UpdateUI() Silverlight function is executed on the UI thread and passed the num and str parameters.

Silverlight controls provide the CheckAccess() function to determine if the control is accessible from the current thread. The CheckAccess() function returns true if the current thread can update the control and false if it cannot. For example, the following code checks to see if a TextBlock control can be accessed by the current thread before updating it:

if (myText.CheckAccess())

myText.Text = "Updated";

Updating progress of a BackgroundWorker thread

BackgroundWorker threads also support the ability to update the Silverlight application of their progress. This is done by setting the WorkerSupportsProgress property of the BackgroundWorker object to true and then attaching a ProgressChanged event handler to the object before the BackgroundWorker thread is started. For example:

worker.WorkerReportsProgress = true; worker.ProgressChanged +=

new ProgressChangedEventHandler(UpdateProgress);

The ProgressChanged event handler is triggered inside the BackgroundWorker thread code by calling the ReportProgress() function of the object. ReportProgress() accepts an integer, representing the percentage complete that should range from 0 to 100, as an argument. For example, to report back a progress of 50 percent, use the following code:

worker.ReportProgress(50);

The ProgressChanged event handler actually runs on a UI thread, so you can access the Silverlight controls in the application and update them. The ProgressChanged event hander is a great way to implement a progress bar.

Cancelling a BackgroundWorker thread

An important feature of BackgroundWorker threads is the ability to cancel them if necessary. This feature must be enabled before the thread is started.

To enable cancelling a BackgroundWorker thread, set the WorkerSupportsCancellation property of the object to true. For example:

worker.WorkerSupportsCancellation = true;

Then the BackgroundWorker thread can be cancelled from the UI thread using the CancelAsync() function of the object. For example:

worker.CancelAsync();

The CancelAsync() function sets the CancellationPending property of the BackgroundWorker object to true. It does not halt the thread for you. This gives you the ability to gracefully end the thread. To gracefully end the thread, monitor the

CancellationPending property and if it is true, clean up and return from the thread. On the way out, you need to set the Cancel property of the DoWorkEventArgs to true. For example, the following code shows a basic way to cancel a BackgroundWorker thread:

if (worker.CancellationPending) {

e.Cancel = true; return;

Handling completion of a BackgroundWorker thread

The BackgroundWorker class also provides a completion event that enables you to clean up after the thread and handle any errors. This is done attaching a RunWorkerCompleted event handler to the object before the BackgroundWorker thread is started. For example:

worker.RunWorkerCompleted +=

new RunWorkerCompletedEventHandler(DoBackgroundCompleted);

The RunWorkerCompleted event handler is triggered upon returning from the DoWork event handler function. The status of the thread can be determined by the Cancel, Error, and Result attributes of the RunWorkerCompletedEventArgs passed to the handler. Cancel is a boolean type, Error is an error type, and Result is an object type.

The Cancel and Result properties can be set in the BackgroundWorker code prior to returning from the handler. The Error property is set if the worker thread returns an unhandled error.

Example: BackgroundWorker thread application

Listings 13.13 and 13.14 show an example of implementing BackgroundWorker threads in a Silverlight application. The XAML code in Listing 13.13 implements a Canvas control named sky on which the application places stars.

The TextBox control numText allows users to specify the number of stars. The Button controls loadButton and cancelButton are used to begin and cancel a BackgroundWorker thread. The Canvas control progressIndicator contains a TextBlock control named progressText, and a Rectangle control named progressBar, that are used to implement a status bar showing percentage complete.

LISTING 13.13

XAML Code That Defines a Black Canvas Control That Is Used as a Night Sky and Two Button Controls to Start and Cancel a BackgroundWorker Thread

<UserControl x:Class="backgroundthread.Page"

xmlns="http://schemas.microsoft.com/client/2 007" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="400" Height="300">

<Grid x:Name=MLayoutRootM Background=MDarkBlueM> <Canvas x:Name="sky"

Background=MBlackM VerticalAlignment=MTopM Width=M400M Height=M250M /> <TextBox x:Name=MnumTextM Text="100" BorderThickness=M2M Height=M3 0M Width="75" Margin=M-250,0,0,10M VerticalAlignment=MBottomM/> <Button x:Name="loadButton" Content=MLoad Stars" Height="3 0" Width="100" Margin=M-50,0,0,10M VerticalAlignment=MBottomM/> <Button x:Name=McancelButtonM Content=MCancelM Height=M3 0M Width="100" Margin=M220,0,0,10M VerticalAlignment=MBottomM/> <Canvas x:Name="progIndicator" Opacity=".5n Background=MGrayM Height="25" Width="100" VerticalAlignment="Center" HorizontalAlignment=MCenterM> <Rectangle x:Name=MprogressBarM Height="25" Width="0" Fill="Blue" /> <TextBlock x:Name=MprogressTextM Foreground=MWhiteM Canvas.Left="4" />

The code in Listing 13.14 imports the System.Windows.Threading library and defines a BackgroundWorker class named bgWork. In the Page() constructor function, the bgWork object is instantiated and the WorkerReportsProgress and WorkerSupportsCancellation properties are set to true.

The constructor function also attaches DoWork, ProgressChanged, and RunWorkerCompleted event handlers to the bgWork object. A Click event handler named loadButton_Click() is attached to the loadButton Button control and a Click event handler named cancelButton_Click() is attached to the cancelButton Button control.

Inside the loadButton_Click() event handler, the code gets the number of stars from the numText TextBlock and calls the RunWorkerAsync() function of the bgWorker object to begin thread execution.

Inside the cancelButton_Click() event handler, the code calls the CancelAsync() function of the bgWorker object to notify the bgWorker thread to halt execution.

The bgWorker object's DoWork event handler DoBackground() creates a delegate object that points to the AddStar() function using the following line of code:

AddStarDelegate updateUI = new AddStarDelegate(AddStar);

Then the code implements a for loop that randomly sets X and Y coordinates and invokes the delegate function AddStar() to add an Ellipse to the sky Canvas at coordinates X and Y using the following line of code:

this.Dispatcher.BeginInvoke(updateUI, X, Y);

In each pass through the for loop, the code calls the ReportProgress() function of the bgWorker object. When the progress is reported, the DoBackgroundProgressChanged() event handler function is called, which updates the progressBar and progressText objects in the status bar UI.

The DoBackgroundCompleted() event handler is called when the DoWorker() event handler finishes. The DoBackgroundCompleted() checks the Cancelled and Error properties of the RunWorkerCompletedEventArgs object and updates the progressText control.

The results are shown in Figure 13.6. When the user clicks Load Stars, the bgWorker thread launches and begins adding stars to the Canvas. If the user clicks Cancel, the bgWorker thread stops adding stars and returns. The progress bar is updated each time a star is added to the canvas.

LISTING 13.14

C# Code That Implements a BackgroundWorker Thread to Add Stars to a Black Canvas Control using System;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Media;

using System.Windows.Media.Animation;

using System.Windows.Shapes;

//Add Background thread support using System.Windows.Threading;

namespace backgroundthread {

public partial class Page : UserControl {

delegate void AddStarDelegate(int X, int Y); BackgroundWorker bgWork;

InitializeComponent();

//Setup Background Worker Thread bgWork = new BackgroundWorker(); bgWork.WorkerReportsProgress = true; bgWork.WorkerSupportsCancellation = true; bgWork.DoWork +=new DoWorkEventHandler(DoBackground); bgWork.ProgressChanged +=

new ProgressChangedEventHandler(DoBackgroundProgressChanged); bgWork.RunWorkerCompleted +=

new RunWorkerCompletedEventHandler(DoBackgroundCompleted);

loadButton.Click += new RoutedEventHandler(loadButton_Click); cancelButton.Click +=

new RoutedEventHandler(cancelButton_Click);

void cancelButton_Click(object sender, RoutedEventArgs e) {

bgWork.CancelAsync();

void loadButton_Click(object sender, RoutedEventArgs e) {

int numStars = Convert.ToInt3 2(numText.Text);

//Start Background Worker bgWork.RunWorkerAsync(numStars);

void DoBackground(object sender, DoWorkEventArgs e) {

BackgroundWorker worker = sender as BackgroundWorker; AddStarDelegate updateUI = new AddStarDelegate(AddStar); int X, Y, wait, starCount, percent;

starCount = Convert.ToInt32(e.Argument);

//Check for Cancel if (worker.CancellationPending) {

e.Cancel = true; return;

X = new Random().Next(10, 3 90); wait = new Random().Next(100, 500); System.Threading.Thread.Sleep(wait); Y = new Random().Next(10, 240);

//Invoke Delegate Function to Update UI this.Dispatcher.BeginInvoke(updateUI, X, Y);

//Notify Progress Event Handler percent = Convert.ToInt32(((double)cnt /

(double)starCount) * 100); worker.ReportProgress(percent);

protected void DoBackgroundProgressChanged(object sender,

ProgressChangedEventArgs e)

progressText.Text = String.Format("Loading {0}%", e.ProgressPercentage.ToString()); progressBar.Width = e.ProgressPercentage;

continued

LISTING 13.14

(continued)

void DoBackgroundCompleted(object sender,

RunWorkerCompletedEventArgs e)

if (e.Cancelled)

progressText.Text = "Cancelled"; else if (e.Error != null)

progressText.Text = "Error";

else progressText.Text = "Completed";

Ellipse star = new Ellipse();

star.SetValue(Canvas.LeftProperty, X); star.SetValue(Canvas.TopProperty, Y); star.Fill = new SolidColorBrush(Colors.White); sky.Children.Add(star);

FIGURE 13.6

Silverlight application that implements a BackgroundWorker thread that adds stars to a Canvas control and updates a progress bar

FIGURE 13.6

0 0

Post a comment

  • Receive news updates via email from this site