Using the BackgroundWorker in a Silverlight MVVM Application

With Silverlight 4 and the Entity Framework you get a lot of work done on your behalf in terms of standard UI CRUD-style operations. Validations and I/O are pretty easy to accommodate out of the box. But sometimes you need to perform some long running tasks either on the client or on the server via service calls. To prevent your UI from hanging and annoying your users, you should consider placing these operations on a background thread. The BackgroundWorker object is the perfect solution for this. It is easy to use and easy to debug. There are, however, a few things you should understand about where and when the BackgroundWorker can provide feedback and/or manipulate the UI. Spending a few minutes building out the sample listed here should give you the foundation you need to implement very complex, very responsive scenarios.

Note that I used MVVM Light’s RelayCommand not because it’s required but because it’s simple and provides the functionality I required in other applications when binding commands to UI events other than Click. The ViewModelBase also automatically implements INotifyPropertyChanged which is nice.

Here is how the system should operate:

The user types a message into the text box and presses the button. The message is passed into the processor object on a background thread. The processor occasionally notifies the UI that status has changed and the UI refreshes accordingly. While the processor is executing, there should be no lag in the UI functionality. This could also be enhanced to allow the processor to handle in-transit updates to the processor’s payload from the UI but this is beyond the scope of this post.

Getting Started

Start by opening Visual Studio 2010 and creating a new Silverlight Application. No need to do anything special here, no Navigation app, no MVM Light app. Inside the Silverlight application (I named mine BackgroundTest) create three new folders:

·         Models

·         ViewModels

·         Views

Drag the MainPage.xaml from the root folder into the Views folder. This is not really required but it keeps things organized.

Right click on ViewModels and select Add Item => MvvmViewModel. Name it MainViewModel. If you don’t have MvvmViewModel as an item template, check out Laurent Bugnion’s site for details about the MVVM Light Toolkit. http://www.galasoft.ch/

Add two new classes to the Models folder:

·         LongRunningObject: This is our faux object that will take an arbitrary amount of time to process the ProcessItem it receives.

·         ProcessItem: This is a simple business object that implements INotifyPropertyChanged.

In the App.xaml file, add a reference to the ViewModels namespace of the current project and give it a prefix of “vm”:

xmlns:vm="clr-namespace:BackgroundTest.ViewModels"
 

In the Application Resources element (also in App.xaml) add a static reference to the MainViewModel:

<vm:MainViewModel x:Key="MainViewModel" />

 

By adding the MVVM Light ViewModel you should have automatically added references to the following DLL’s. If they are not there, add them now:

·         GalaSoft.MvvmLight.Etras.SL4

·         GalaSoft.MvvmLight.SL4

Here is the Xaml of the MainPage:

<UserControl x:Class="BackgroundTest.MainPage"
      xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
      xmlns:cmd="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.SL4"
      xmlns:vm="clr-namespace:BackgroundTest.ViewModels"
      mc:Ignorable="d"
       DataContext="{Binding Source={StaticResource MainViewModel}}"
       Height="250" Width="500">
       
     <UserControl.Resources>
             <DataTemplate x:Key="ProcItemsTemplate">
                   <Grid>
                         <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="65" />
                                <ColumnDefinition />
                                <ColumnDefinition Width="30" />
                         </Grid.ColumnDefinitions>
                         <TextBlock Grid.Column="0"
                                      Text="{Binding Path=Progress, StringFormat=\{0:P\}}" />
                         <TextBlock Grid.Column="1"
                                      Text="{Binding Path=CommandData}" />
                         <TextBlock Grid.Column="2"
                                      Text="{Binding Path=IsComplete}" />
                   </Grid>
             </DataTemplate>
      </UserControl.Resources>
 
       <Grid x:Name="LayoutRoot" Background="Beige">
             <Grid.RowDefinitions>
                   <RowDefinition Height="25" />
                   <RowDefinition Height="25" />
                   <RowDefinition />
             </Grid.RowDefinitions>
             <Grid.ColumnDefinitions>
                   <ColumnDefinition Width="140" />
                   <ColumnDefinition />
             </Grid.ColumnDefinitions>
              <TextBox Text="{Binding Path=DataText, Mode=TwoWay}"
                       Width="200" TextWrapping="Wrap" Grid.ColumnSpan="2" />
             <Button Content="Load this and wait..."
                      Grid.Row="1" Width="150" Grid.ColumnSpan="2"
                      Margin="0,2,2,0" >
                   <i:Interaction.Triggers>
                         <i:EventTrigger EventName="Click">
                                <cmd:EventToCommand
                                      Command="{Binding StartLongRunningExecCommand}"
                                       CommandParameter="{Binding Path=DataText}" />
                         </i:EventTrigger>
                   </i:Interaction.Triggers>
             </Button>
             <Rectangle Grid.Row="2" Fill="{Binding Path=CurrentColor}" />
             <StackPanel Orientation="Vertical"
                          HorizontalAlignment="Center"
                          VerticalAlignment="Top" Grid.Row="2">
                   <TextBlock Text="Red=Not Running" />
                   <TextBlock Text="Green=Running" />
                   <TextBlock Text="{Binding Path=ProcessorCount}"  />
             </StackPanel>
             <ScrollViewer Grid.Row="2" Grid.Column="2">
                   <ItemsControl
                          ItemsSource="{Binding Path=TokenList, Mode=TwoWay}"
                          ItemTemplate="{StaticResource ProcItemsTemplate}" />
             </ScrollViewer>
      </Grid>
</UserControl> 
 

This is pretty straight forward. The DataContext of the control is set to the key that you added previously in the App.xml. The binding statements will be more apparent after you dig into the ViewModel. Also, the Click event of the button is bound using Triggers to show that this command could be fired from any event, not just a button. The parameter of the command is set to the text property of the text box.

There is absolutely no code behind other than Initialize in the view which is exactly where we want to be.

Let’s now provide some meat to the business object.

If you fail to implement the INotifyPropertyChanged interface in this business object, updating items in the list will not refresh in the UI regardless of how many times and in how many places you throw the RaisePropertyChanged event.
 

We have three properties: A progress percentage value (double), a string body and a boolean complete flag. In addition there is a private implementation of that calls the PropertyChanged event for each property.

Here is the code:

using System.ComponentModel; namespace BackgroundTest.Models
{
     public class ProcessItem : INotifyPropertyChanged
     {
          
          private double _progress = 0;
          public double Progress
           {
              get
              {
                   return _progress;
              }
              set
               {
                   if (_progress != value)
                   {
                        _progress = value;
                        RaisePropertyChanged("Progress");
                   }
              }
           }
     
          private string _commandData = string.Empty;
          public string CommandData
           {
              get
              {
                   return _commandData;
              }
              set
              {
                   if (_commandData != value)
                   {
                        _commandData = value;
                        RaisePropertyChanged("CommandData");
                   }
              }
          }
   
          private bool _isComplete = false;
          public bool IsComplete
          {
              get
              {
                   return _isComplete;
              }
              set
              {
                   if (_isComplete != value)
                   {
                        _isComplete = value;
                        RaisePropertyChanged("IsComplete");
                   }
              }
          }
  
          public event PropertyChangedEventHandler PropertyChanged;
          private void RaisePropertyChanged(string prop)
          {
              if (PropertyChanged != null)
              {
                   PropertyChanged(this, new PropertyChangedEventArgs(prop));
              }
          }
      }
} 

 

The Long Running Processor

Now lets take a look at LongRunningObject. Remember that this is a pseudo object that simulates a long running call to a web service or some heavy client-side processing that must take place.

We have two public events that fire during processing to notify clients of progress and to state that processing is complete.

When the single public method fires, the events are thrown and a for loop with a sleep timer is used to simulate a long process. Once the loop is complete, the completion event is thrown with the appropriate payload.

Note that I’m throwing a new ProcessItem with each event. This is not completely necessary but the goal is to indicate that anything can come back, not just the input parameter. Remember, this is occuring on a different thread and care should be taken when passing references around in this manner.

Here is the code:

using System;
using System.Threading;
 
namespace BackgroundTest.Models
{
     public class LongRunningObject
     {
          public event Action<ProcessItem> WorkCompleted;
          public event Action<ProcessItem> WorkProgress;
          
          public void RunWorkAsync(ProcessItem inputData)
          {
              double iMax = 25;
              // Throw starting progress event
              WorkProgress(new ProcessItem()
                    {
                         Progress = 0,
                         CommandData = inputData.CommandData
                    });
 
              for (double i = 0; i < iMax; i++)
              {
                   Thread.Sleep(1200);
                   double dProg = (i + 1) / iMax;
                   // Throw the current progress event
                   WorkProgress(new ProcessItem()
                         {
                              Progress = dProg,
                              CommandData = inputData.CommandData,
                             IsComplete = false
                        });
              }
              // Throw the completed event
              WorkCompleted(new ProcessItem()
                    {
                         Progress = 1,
                         CommandData = inputData.CommandData,
                        IsComplete = true
                   });
          }
     }
}
 

That’s it for plumbing and faux code. Now let’s open the MainViewModel and get the real work done.

The ViewModel

First, we’ll add the properties required by the UI. We’ll need a property to bind the RelayCommand (StartLongRunningExecCommand), a string property to bind to the UI TextBlock (DataText), a simple counter to track how many BackgroundWorker objects we have in process (ProcessorCount) and an ObservableCollection to store all the items being processed by the BackgroundWorker (TokenList). Each of these properties will execute the RaisePropertyChanged event in the setter if the source value changes. This means that you should always update the value by setting the property, not hitting the storage field directly if you want the UI to update it’s binding sources.

We also need a local private variable to store the UI thread’s Dispatcher. This will be our means of updating the UI when progress is received from the BackgroundWorker object.

private Dispatcher currentDispatcher;
 

We’ll wire this up in the constructor:

currentDispatcher = App.Current.RootVisual.Dispatcher;
 

The actual work that takes place happens because in the constructor we also wire up the RelayCommand to a method.

StartLongRunningExecCommand = new RelayCommand<string>(p =>
     {
          StartProcess(p);
     });
 

The StartProcess method first instances the events that will be handled during processing:

DoWorkEventHandler workHandler = null;
RunWorkerCompletedEventHandler doneHandler = null;
Action<ProcessItem> longRunProgress = null;
Action<ProcessItem> longRunCompleted = null;
 

Then we set up the BackgroundWorker, assign it’s events and begin the work. Note that inside each delegate event that should fire only once, we immediately unwire the event from the source object to prevent memory leaks from orphaned objects. This makes it very plain that an object can be garbage collected. For events that fire multiple times, care should be taken to unwire those events upon final cleanup. Here we do it when the WorkCompleted event fires from the business object. Additionally, note that we call the currentDispatcher.BeginInvoke method in various places. This allows the method passed to be called on the original UI thread.

Here is the entire StartProcess method:

 
public void StartProcess(string commandData)
{
     // Events used during background processing
     DoWorkEventHandler workHandler = null;
     RunWorkerCompletedEventHandler doneHandler = null;
     Action<ProcessItem> longRunProgress = null;
     Action<ProcessItem> longRunCompleted = null;
  
     // Implementation of the BackgroundWorker
     var wrkr = new BackgroundWorker();
     wrkr.DoWork += workHandler =
 
          delegate(object oDoWrk, DoWorkEventArgs eWrk)
          {
              // Unwire the workHandler to prevent memory leaks
              wrkr.DoWork -= workHandler;
              LongRunningObject LongRun = new Models.LongRunningObject();
              LongRun.WorkProgress += longRunProgress =
                   delegate(ProcessItem result)
                   {
                        // Call the method on the UI thread so that we can get
                        // updates and avoid cross-threading exceptions.
                        currentDispatcher.BeginInvoke(
                             new Action<ProcessItem>(AddToken), result);
                   };
              LongRun.WorkCompleted += longRunCompleted =
                   delegate(ProcessItem result)
                   {
                        // Unwire all events for this instance
                        // of the LongRunningObject
                        LongRun.WorkProgress -= longRunProgress;
                        LongRun.WorkCompleted -= longRunCompleted;
                        currentDispatcher.BeginInvoke(
                             new Action<ProcessItem>(AddToken), result);
                   };
 
               // Events are wired for the business object,
               // this where we start the actual work.
              LongRun.RunWorkAsync(
                   new ProcessItem() { Progress = 0, CommandData = dataText });
          };
     wrkr.RunWorkerCompleted += doneHandler =
          delegate(object oDone, RunWorkerCompletedEventArgs eDone)
          {
              // Work is complete, decrement the counter
              // and kill references to teh donHandler.
              wrkr.RunWorkerCompleted -= doneHandler;
              procCount--;
              RaisePropertyChanged("ProcessorCount");
          };
      // This is where the actual asynchronous process will
      // start performing the work that is wired up in the
      // previous statements.
     wrkr.RunWorkerAsync();
     procCount++;
     RaisePropertyChanged("ProcessorCount");
}
 

The AddToken method is a simple synchronizer that eitehr adds the text value to the UI-bound list or updates the progress value. Once again, if you did not implement INotifyPropertyChanged in your business object, setting the itm.Progress and itm.IsComplete values would not update values on the UI thread.

That’s pretty much it. I’ve included the complete code listing for the Silverlight side here.

http://weblogs.asp.net/blogs/jimjackson/Files/BackgroundTest.zip

 

2 Comments

Comments have been disabled for this content.