Welcome to Pete Brown's 10rem.net

First time here? If you are a developer or are interested in Microsoft tools and technology, please consider subscribing to the latest posts.

You may also be interested in my blog archives, the articles section, or some of my lab projects such as the C64 emulator written in Silverlight.

(hide this)

Asynchronous Web and Network Calls on the Client in WPF (and Silverlight and .NET in general)

Pete Brown - 17 February 2011

An asynchronous network call is one in which you fire off a method and don't block the thread while waiting for it to return. This introduces some complexities since you have to somehow be notified of the success or failure of the call, and need to somehow retrieve the results.

Silverlight developers are used to making asynchronous network calls because that's all the stack has supported since first released. However, WPF developers can benefit from a little async code in their applications to help make things a bit more responsive.

Solution Setup

I created a solution into which I put a regular WPF Windows application and an empty ASP.NET Web application.

Services Project

Into the ASP.NET Web Application, I added a "Silverlight-enabled WCF Service". Why that template? It's a super simple approach to creating WCF services that act like regular old SOAP services. It keeps the example easy to follow.

The service is in a Services subfolder on the web project and is named CustomerService.svc. Here's the code:

using System;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.Collections.Generic;

namespace WebServicesSite.Services
{
[ServiceContract(Namespace = "")]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class CustomerService
{
[OperationContract]
public IList<Customer> GetCustomers()
{
List<Customer> results = new List<Customer>();

results.Add(new Customer() { LastName = "Brown", FirstName = "Pete" });
results.Add(new Customer() { LastName = "Stagner", FirstName = "Joe" });
results.Add(new Customer() { LastName = "Liberty", FirstName = "Jesse" });
results.Add(new Customer() { LastName = "Galloway", FirstName = "Jon" });

return results;
}


}
}

In the same folder on the ASP.NET Service project, I added a class named "Customer":

namespace WebServicesSite.Services
{
public class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
}

Once you have the service set up, you'll want to fix the web project's port just to ensure that you don't hang the service and get stuck with a new port the next time you run. That results in your app not finding the service and can be hard to debug unless you know what you're looking for.

image

The actual port number isn't that important; I used the default. Just be sure to set to Specific port to lock in that port number. Silverlight developers: this isn't a bad idea when working on your own web services in your projects.

Next, add a service reference from your WPF project to the new service in the ASP.NET project (be sure to build the solution first). I named the namespace "Services"

image

Before clicking "OK", click the "Advanced…" button and select the option to generate the asynchronous client operations. In Silverlight, this is on by default as there's no other option. In WPF, however, async operations are less typical.

image

If you received an error downloading the metadata, you need to successfully build the web project first.

Next, we'll need to build some basic UI.

User Interface

I used the designer in Visual studio 2010 to create a dead simple user interface. (In Xaml, I changed Name to x:Name as is my habit), and added a simple item template to the ListBox. Here's the markup:

<Window x:Class="WpfAsyncExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Async Service Example"
Height="350"
Width="525">
<Grid>
<Button Content="Call Service"
x:Name="CallService"
Height="23"
HorizontalAlignment="Left"
Margin="12,12,0,0"
VerticalAlignment="Top"
Width="75"
Click="CallService_Click" />
<ListBox HorizontalAlignment="Left"
Margin="12,51,0,12"
x:Name="CustomerList"
Width="479">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding LastName}" />
<TextBlock Text=", " />
<TextBlock Text="{Binding FirstName}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>

The first approach we'll look at is the simple event-based approach. So double-click that button and create an event handler we can use in the remaining examples.

The Event Approach

When it's possible to use the event approach, this is by far the easiest way to handle asynchronous calls, and is the approach used most often in Silverlight code as well.

private void CallService_Click(object sender, RoutedEventArgs e)
{
var client = new Services.CustomerServiceClient();

client.GetCustomersCompleted += (s, ev) =>
{
CustomerList.ItemsSource = ev.Result;
};

client.GetCustomersAsync();
}

To keep the code even tighter, I used an anonymous delegate and lambda expression to set up the event handler. You could also break it apart into a separate method, but I personally find that muddies the code.

The event approach is nice because you don't typically need to worry about threading and marshaling a result back to the UI thread; that's taken care of for you.

The next approach is a bit more flexible, but is also more complex.

The Callback Approach

Another common approach is the callback approach. I often think of this as the bare-bones method, as it is definitely a level or two lower than the event approach. However, you gain some additional flexibility in that there are no threading assumptions made, and you can pass around your own state objects.

private void CallService_Click(object sender, RoutedEventArgs e)
{
// we'll need this to update the UI
SynchronizationContext context = SynchronizationContext.Current;

var client = new Services.CustomerServiceClient();

// this can be anything: a custom class or whatever you need
// to track state from the call to the response
CustomerRequestState requestState = new CustomerRequestState();
requestState.Client = client;
requestState.Context = context;

IAsyncResult asyncResult = client.BeginGetCustomers(
new AsyncCallback(ResponseCallback), requestState);
}

private void ResponseCallback(IAsyncResult asyncResult)
{
CustomerRequestState asyncState = asyncResult.AsyncState as CustomerRequestState;

Customer[] customers = asyncState.Client.EndGetCustomers(asyncResult);

// you can't just do this, as you'll get a cross-thread access error
//CustomerList.ItemsSource = customers;

// use SynchronizationContext and an anonymous delegate
// to update the listbox in the UI
asyncState.Context.Post((o) =>
{ CustomerList.ItemsSource = customers; }, null);

}

To make this work, we'll also need to create a state object to hold the bits we want to pass from the call to the response loading. I decided to track the calling WCF client (we'll need a reference to that), the synchronization context, and an arbitrary bit of data I don't actually use. Your own object can contain as little or as much as you need.

namespace WpfAsyncExample
{
class CustomerRequestState
{
public string AnythingYouWant { get; set; }
public CustomerServiceClient Client { get; set; }
public SynchronizationContext Context { get; set; }
}
}

Now, I mentioned that I am passing around a SynchronizationContext. That object allows us to make calls back on the original calling thread, in this case, the UI thread. We need to be able to marshal calls to that thread in order to update the user interface. If you try and update the UI from a non-UI thread, you'll get a cross-thread access exception.

Now, to keep things simple and understandable in the above code, I haven't done any error handling or proper disposal of objects. Make sure you do that in your production code.

The final approach we'll look at builds upon what we've seen so far, but makes it easier to do things like chain successive calls without all the cruft you'd normally use.

Using Reactive Extensions

The final example uses an additional library knows as "Reactive Extensions for .NET" or "Rx". This library does a ton of things to help handle asynchronous operations without making your code into an unintelligible bowl of calls and response callbacks.

First, get Reactive Extensions. You can either download it from the main site, or better yet, use NuGet to add it directly to your project. The NuGet approach will save you a ton of time. Learn how to use NuGet in your WPF projects here.

PM> install-package Rx-All
'Rx-Core (≥ 1.0.2856.0)' not installed. Attempting to retrieve dependency from source...
Done
'Rx-AsyncLinq (≥ 1.0.2856.0)' not installed. Attempting to retrieve dependency from source...
Done
'Rx-Interactive (≥ 1.0.2856.0)' not installed. Attempting to retrieve dependency from source...
Done
'Rx-Main (≥ 1.0.2856.0)' not installed. Attempting to retrieve dependency from source...
Done
'Rx-ClientProfile (≥ 1.0.2856.0)' not installed. Attempting to retrieve dependency from source...
Done
'Rx-ExtendedProfile (≥ 1.0.2856.0)' not installed. Attempting to retrieve dependency from source...
Done
'Rx-Testing (≥ 1.0.2856.0)' not installed. Attempting to retrieve dependency from source...
Done
You are downloading Rx-Core from Microsoft Corporation, the license agreement to which is available at http://go.microsoft.com/fwlink/?LinkID=186234. Check the package for additional dependencies, which may come with their own license agreement(s). Your use of the package and dependencies constitutes your acceptance of their license agreements. If you do not accept the license agreement(s), then delete the relevant components from your device.
Successfully installed 'Rx-Core 1.0.2856.0'
You are downloading Rx-AsyncLinq from Microsoft Corporation, the license agreement to which is available at http://go.microsoft.com/fwlink/?LinkID=186234. Check the package for additional dependencies, which may come with their own license agreement(s). Your use of the package and dependencies constitutes your acceptance of their license agreements. If you do not accept the license agreement(s), then delete the relevant components from your device.
Successfully installed 'Rx-AsyncLinq 1.0.2856.0'
You are downloading Rx-Interactive from Microsoft Corporation, the license agreement to which is available at http://go.microsoft.com/fwlink/?LinkID=186234. Check the package for additional dependencies, which may come with their own license agreement(s). Your use of the package and dependencies constitutes your acceptance of their license agreements. If you do not accept the license agreement(s), then delete the relevant components from your device.
Successfully installed 'Rx-Interactive 1.0.2856.0'
You are downloading Rx-Main from Microsoft Corporation, the license agreement to which is available at http://go.microsoft.com/fwlink/?LinkID=186234. Check the package for additional dependencies, which may come with their own license agreement(s). Your use of the package and dependencies constitutes your acceptance of their license agreements. If you do not accept the license agreement(s), then delete the relevant components from your device.
Successfully installed 'Rx-Main 1.0.2856.0'
You are downloading Rx-ClientProfile from Microsoft Corporation, the license agreement to which is available at http://go.microsoft.com/fwlink/?LinkID=186234. Check the package for additional dependencies, which may come with their own license agreement(s). Your use of the package and dependencies constitutes your acceptance of their license agreements. If you do not accept the license agreement(s), then delete the relevant components from your device.
Successfully installed 'Rx-ClientProfile 1.0.2856.0'
You are downloading Rx-ExtendedProfile from Microsoft Corporation, the license agreement to which is available at http://go.microsoft.com/fwlink/?LinkID=186234. Check the package for additional dependencies, which may come with their own license agreement(s). Your use of the package and dependencies constitutes your acceptance of their license agreements. If you do not accept the license agreement(s), then delete the relevant components from your device.
Successfully installed 'Rx-ExtendedProfile 1.0.2856.0'
You are downloading Rx-Testing from Microsoft Corporation, the license agreement to which is available at http://go.microsoft.com/fwlink/?LinkID=186234. Check the package for additional dependencies, which may come with their own license agreement(s). Your use of the package and dependencies constitutes your acceptance of their license agreements. If you do not accept the license agreement(s), then delete the relevant components from your device.
Successfully installed 'Rx-Testing 1.0.2856.0'
You are downloading Rx-All from Microsoft Corporation, the license agreement to which is available at http://go.microsoft.com/fwlink/?LinkID=186234. Check the package for additional dependencies, which may come with their own license agreement(s). Your use of the package and dependencies constitutes your acceptance of their license agreements. If you do not accept the license agreement(s), then delete the relevant components from your device.
Successfully installed 'Rx-All 1.0.2856.0'
Successfully added 'Rx-Core 1.0.2856.0' to WpfAsyncExample
Successfully added 'Rx-AsyncLinq 1.0.2856.0' to WpfAsyncExample
Successfully added 'Rx-Interactive 1.0.2856.0' to WpfAsyncExample
Successfully added 'Rx-Main 1.0.2856.0' to WpfAsyncExample
Successfully added 'Rx-ClientProfile 1.0.2856.0' to WpfAsyncExample
Successfully added 'Rx-ExtendedProfile 1.0.2856.0' to WpfAsyncExample
Successfully added 'Rx-Testing 1.0.2856.0' to WpfAsyncExample
Successfully added 'Rx-All 1.0.2856.0' to WpfAsyncExample

PM>

While that added a bit more than we'll use in this example, you can control exactly what you get by only installing those packages. Rx-All was the easiest thing for me to do for this example. There are more than a few ways we can use Rx here, so I'm going to concentrate on just one: using the event approach.

private void CallService_Click(object sender, RoutedEventArgs e)
{
// we'll need the UI thread just like last time
SynchronizationContext context = SynchronizationContext.Current;

var client = new Services.CustomerServiceClient();

// Wait for the GetCustomersCompleted event to finish. When
// it finishes, get the results from the event and
// using the thread context from "context", set the
// customer listbox item source to the result
var o = Observable.FromEvent<GetCustomersCompletedEventArgs>(
client, "GetCustomersCompleted")
.ObserveOn(context)
.Select(result => result.EventArgs.Result)
.Subscribe(s => CustomerList.ItemsSource = s);

// kick it off
client.GetCustomersAsync();
}

The code here initially looks as complex as the previous versions, or at least as complex as the event version. However, one subtle difference is that it has provided a nice way for us to chain additional dependent calls. For example, so you need to first call to get a customer ID, then make a separate call to get all the invoices for that customer. That could involve some pretty ugly call chaining in either the event approach or the callback approach. The Rx approach will allow you to more easily chain those calls together.

image

When you run the application, in all three approaches, you should see the results shown here. Of course, what I have presented is a simple example. In a real application, both the effort and the benefits are greater.

Why is this even Necessary?

Like many of the practices we follow, it's not strictly necessary. However, if you want to have a better user experience, using either a spawned thread or an async call for service calls can help keep your UI responsive.

Using async network calls also helps make your code and skills more compatible with Silverlight. All the code here is compatible with Silverlight.

Consider using async operations in your next desktop application.

               

Source Code and Related Media

Download /media/74113/wpfasyncexample.zip
posted by Pete Brown on Thursday, February 17, 2011
filed under:                

12 comments for “Asynchronous Web and Network Calls on the Client in WPF (and Silverlight and .NET in general)”

  1. Niteshsays:
    Thanks for post.
    Yesterday only, one of my colleague was looking for ways to show a cancellable progress bar for a blocking synch. service call in WPF application and I suggested doing similar to Silverlight. We were planning to search for the ways to make async calls today. The timing of your post couldn't have been any better for us.
  2. MilanCHEsays:
    Hello, simple and powerful article!

    What is the best way to provide templates for each control; for example Ready ( When control is shown), Loading ... (Waiting for loading), Loaded ... (When we have results), Empty ( When we get empty results ) and Error( When we get an error) and to update status on UI with Asynchronous Web and Network Calls?

    And one more question: is it better to use Rx or Async CTP?
  3. Seansays:
    Good article. One thing I have a question on, when you mention that the callback approach lets you pass around your own user state objects, how does this differe from using the userstate overload on the generated asynchronous methods on the webservice object? We do this using the event approach in our applications and it works fine!

    Sean
  4. Paulussays:
    Dear Pete,

    Assuming "CustomerList" belongs to a TabItem in a WPF TabControl, and the user tabs onto another
    TabItem whilst the service call is going on: "CustomerList" is unloaded from the UIThread.

    Does this behavior have any impact on any piece of the code above: i.e. are any precautions in order or will the
    result of the async call "gracefully vanish"?

    Thanks for this excellent article, Paulus.
  5. Petesays:
    @Paulus

    That's where using a pattern like MVVM really helps out. It's better to control the lifetime of the data at a ViewModel level rather than at the UI level.

    I haven't tried scenarios like that *without* a ViewModel.

    Pete
  6. Craigologysays:
    Hi Pete,

    Regularly follow your articles - another fine post.

    The above is great when you're using the out-of-the-box Service Reference / generated proxy approach. In our case however, we use the service contract as a true service abstraction and resolve it in integration mode using IOC into a runtime proxy using WCF's ChannelFactory<> and in unit testing mode into a mockable facade.

    Now, we can wrap our service invocations in a localised background mechanism like TPL Tasks or the non-MVVM-friendly BackgroundWorker. However it would be tidier to keep the async'ness closer to the transport if possible. Also, I can't seem to get TPL Tasks with Continuations to collapse down to a single-threaded blocking call when used in a unit testing context.

    There's a scattering of info out there regarding using async WCF with ChannelFactory<>, including <a href="http://social.msdn.microsoft.com/Forums/eu/async/thread/8ec1b6ef-dbd8-46e6-b0b9-db7bfe80db06"this</a> - promised in .NET 4.5(?)

    Just wondering what your thoughts on this are?

Comment on this Post

Remember me