I've blogged several times about how I like to handle services in Silverlight. For two key posts, take a look at:
- Abstracting Service Calls in Silverlight 3 (works well for 4, too)
- Simplifying Asynchronous Calls in Silverlight using Action
In this post we'll explore the difference between using the actual contract for a WCF service in Silverlight versus using the generated client. The difference is subtle but important, and involves not only the event-based model and the Asynchronous Programming Model (APM) but also some nuances with threads.
There is no code project for this because the nuance is in the way you call the service, not the example itself, but there should be enough code in this post for you to recreate the example if you want to test it yourself.
Here's the rub: let's create a simple service that adds two integers and returns the result. The contract is simply:
[ServiceContract(Namespace = "http://jeremylikness.com/silverlight/")] public interface IAddService { [OperationContract] int Add(int x, int y); }
Now we can implement it - just add the numbers and return the value:
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)] public class AddService : IAddService { public int Add(int x, int y) { return x + y; } }
So that's easy enough (and we've jumped through our first Silverlight hoop by flagging the service with the compatibility attribute). Now we'll fire up a new Silverlight application and reference the service. I'm not doing anything fancy, just discovering the service within the project and letting Silverlight generate the code. Now things get a little more interesting.
Let's add two list boxes that will race each other for results. They are bound to a simple list of integers:
<UserControl.Resources> <DataTemplate x:Key="ListTemplate"> <TextBlock Text="{Binding}"/> </DataTemplate> </UserControl.Resources> <Grid x:Name="LayoutRoot" Background="White"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="1*"/> <ColumnDefinition Width="1*"/> </Grid.ColumnDefinitions> <TextBlock Text="Events"/> <TextBlock Text="APM" Grid.Column="1"/> <ListBox x:Name="Events" ItemsSource="{Binding Series1}" Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Top" ItemTemplate="{StaticResource ListTemplate}"/> <ListBox x:Name="APM" ItemsSource="{Binding Series2}" Grid.Row="1" Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Top" ItemTemplate="{StaticResource ListTemplate}"/> </Grid>
As you might have already guessed, we're going to use the event model and the APM model. Let's wire some code.
First, two collections to hold the results. I also bind the data context to itself so I can bind to the series in the code-behind.
private const int MAX = 999999999; public ObservableCollection<int> Series1 { get; private set; } public ObservableCollection<int> Series2 { get; private set; } public MainPage() { InitializeComponent(); Loaded += (o, e) => LayoutRoot.DataContext = this; Series1 = new ObservableCollection<int>(); Series2 = new ObservableCollection<int>(); if (DesignerProperties.IsInDesignTool) return; _Series1(); _Series2(); }
The Main Event
Now let's implement a Fibonacci sequence using the event-based model:
private void _Series1() { var y = 1; var x = 1; var client = new AddServiceClient(); Series1.Add(1); client.AddCompleted += (o, e) => { x = e.Result; Series1.Add(x); var z = y; y = x; if (x < MAX) { client.AddAsync(x, z); } }; client.AddAsync(x, y); }
Notice how we recursively call the service. What is also important to note, however, is that there is no special use of the dispatcher. I know for a fact this will come back to me on the UI thread. Why? Because I registered the event on the UI thread, and that registration is where the return call will go. I can prove it because despite the fact the series is databound to the UI, it populates without any issue.
Switching to the Asynchronous Programming Model
Now, let's wire the same service using the Asynchronous Programming Model (APM). I'm going to kick it off the same way and in the same context as the event-based version. Notice that because Silverlight creates the client proxy using the event-based model, I actually have to cast the client to the interface in order to take advantage of the APM model.
The APM call adds two methods. One is an AsyncCallback
delegate that will be called when the service is done fetching results, and the other is an optional object to store state (this is passed to the callback so you can reference data from the original call).
private void _Series2() { // add the first item Series2.Add(1); var client = (IAddService) new AddServiceClient(); AsyncCallback end = null; end = ar => { var state = ar.AsyncState as Tuple<IAddService, int>; if (state == null) return; var x = state.Item1.EndAdd(ar); var y = state.Item2; Deployment.Current.Dispatcher.BeginInvoke(() => Series2.Add(x)); if (x < MAX) { state.Item1.BeginAdd(x, y, end, Tuple.Create(state.Item1, x)); } }; client.BeginAdd(1, 1, end, Tuple.Create(client,1)); }
There's a bit going on there - I preserve the original client along with the current value in the state by casting them both into a tuple, and unroll them in the callback. The call is recursive until the max is reached, just like the event model, and using an anonymous method. However, there is a subtle difference between the AddAsync
method we called earlier and the BeginAdd
method here.
The difference? Our "end" callback is not invoked on the UI thread! The APM model lets the result come back on a different thread. If you take out the dispatcher call I use to populate the series and add the value directly, you'll get a cross-thread exception.
So what does this mean?
Most of the time, quite frankly, not much. In my tests, the bulk of the time for the process to run is taken in the service call going over the wire and coming back, and there is little noticeable difference between staying on the UI thread or not (even on a local machine).
However, if you are receiving a result and doing heavy processing before displaying the data, you don't want to return on the UI thread. It's wasteful and can causing blocking, the exact thing the asynchronous pattern is supposed to avoid! While you could send your work to a background worker, the easiest solution is to use the APM model. You'll return on a background thread where you can do all of your heavy lifting and simply dispatch the result when you're ready.
Bottom line: when using services in Silverlight, know where your results land!