Handling Asynchronous Interactions

patterns & practices Developer Center

On this page: Download:
You Will Learn | Using Reactive Extensions | Inside the Implementation Download code samples
Download book as PDF

You Will Learn

  • How to use reactive extensions to create compact code for complex asynchronous operations.

Chapter 2, "Building the Mobile Client,"describes how Tailspin implemented commands in the mobile client application. For some commands, Tailspin implements the command asynchronously to avoid locking the UI while a time-consuming operation is running.

For example, on the AppSettingsView page, a user can enable or disable push notifications of new surveys from the Microsoft Push Notification Service (MPNS). This requires the application to send a request to the MPNS that the application must handle asynchronously. The application displays a progress indicator on the AppSettingsView page while it handles the asynchronous request.

For more information about MPNS, see Chapter 4, "Connecting with Services."

Tailspin decided to use the Reactive Extensions (Rx) for .NET to run asynchronous tasks on the phone because it enables them to create compact, easy-to-understand code for complex asynchronous operations.

Hh821016.note(en-us,PandP.10).gifJana Says:
Jana
                The Reactive Extensions for .NET are a great way to handle how an application interacts with multiple sources of data, such as user input events and web service requests.</td>

Using Reactive Extensions

Rx allows you to write compact, declarative code to manage complex, asynchronous operations. Rx can be understood by comparing it to the more familiar concept of enumerable collections. Figure 3 shows two alternative approaches to iterating over a sequence.

Hh821016.7F558765844A18BADCFCD0D7CAAA1C7A(en-us,PandP.10).png

Figure 3

Enumerable and observable sequences

To iterate over an enumerable sequence, you can use an iterator to pull each item from the sequence in turn, which is what the C# foreach construct does for you. With an observable sequence, you subscribe to an observable object that pushes items from the sequence to you. For example, you can treat the events raised by a control or the data arriving over the network as an observable sequence. Furthermore, you can use standard LINQ operators to filter the items from the observable sequence, and control which thread you use to process each item as it arrives.

Inside the Implementation

The application performs an asynchronous request to the Microsoft Push Notification Service when the user subscribes to push notifications on the AppSettingsView page. The following code example from the AppSettingsView.xaml file shows the definitions of the progress indicator that is active during the asynchronous request and the ToggleSwitch control that enables the user to set his or her preference.

...
xmlns:toolkit="clr-namespace:Microsoft.Phone.Controls;
assembly=Microsoft.Phone.Controls.Toolkit"
...
<shell:SystemTray.ProgressIndicator>
  <shell:ProgressIndicator IsIndeterminate="True" 
    IsVisible="{Binding IsSyncing}"
    Text="{Binding ProgressText}"/>
</shell:SystemTray.ProgressIndicator>
...

<toolkit:ToggleSwitch Header="Subscribe to Push Notifications"  
  Margin="0,202,0,0" 
  IsChecked="{Binding SubscribeToPushNotifications, Mode=TwoWay}" />

The ToggleSwitch control binds to the SubscribeToPushNotifications property of the AppSettingsViewModel, and the ProgressIndicator control binds to the IsSyncing property of the AppSettingsViewModel class.

The following code example from the AppSettingsViewModel class shows what happens when the user clicks the Save button on the AppSettingsView page. This Submit method handles the asynchronous call to the UpdateReceiveNotifications method in the RegistrationServiceClient class by using Rx. Before it calls the UpdateReceiveNotifications method, it first sets the IsSyncing property to true so that the UI can display an active progress meter. The method handles the asynchronous call in three steps:

  1. The UpdateReceiveNotifications method in the RegistrationServiceClient class returns an observable TaskSummaryResult object that contains information about the task.
  2. The Submit method uses the ObserveOnDispatcher method to handle the TaskSummaryResult object on the dispatcher thread.
  3. The Subscribe method specifies how to handle the TaskSummaryResult object and starts the execution of the source observable sequence by asking for the next item.
private readonly IRegistrationServiceClient registrationServiceClient;
private IDisposable subscription;
...

public void Submit()
{
  ...
  this.isSyncing = true;
  ...

  if (this.SubscribeToPushNotifications == 
    this.settingsStore.SubscribeToPushNotifications)
  {
    this.IsSyncing = false;
    if (this.NavigationService.CanGoBack) this.NavigationService.GoBack();
    return;
  }

  ...

  subscription = this.registrationServiceClient.
    .UpdateReceiveNotifications(this.SubscribeToPushNotifications)
    .ObserveOnDisptacher()
    .Subscribe
    ( 
        taskSummary =>
        ... ,
        exception => 
        ...
    );
}

The following code example shows the definition of the action that the Subscribe method performs when it receives a TaskSummaryResult object. If the TaskSummaryResult object indicates that the change was successful, it updates the setting in local isolated storage, sets the IsSyncing property to false, and navigates back to the previous view. If the TaskSummaryResult object indicates that the change failed, it reports the error to the user.

taskSummary =>
{
  if (taskSummary == TaskSummaryResult.Success)
  {
    this.settingsStore.SubscribeToPushNotifications = 
      this.SubscribeToPushNotifications;
    this.IsSyncing = false;
    if (this.NavigationService.CanGoBack) this.NavigationService.GoBack();
    if (!SubscribeToPushNotifications)
    {
      CleanUp();
    }
  }
  else
  {
    // Update unsuccessful, probably due to communication issue with
    // Registration Service. Don't close channel so that we can retry later.
    if (!SubscribeToPushNotifications)
    {
      CleanUp();
    }
    var errorText = TaskCompletedSummaryStrings
      .GetDescriptionForResult(taskSummary);
    this.IsSyncing = false;
    this.submitErrorInteractionRequest.Raise(
      new Notification
      { 
        Title = "Push Notification: Server error", 
        Content = errorText 
      }, 
      n => { });
  }
  this.CanSubmit = true;
}

The Subscribe method can also handle an exception returned from the asynchronous task. The following code example shows how it handles the scenario in the Tailspin mobile client where the asynchronous action throws a WebException exception.

Hh821016.note(en-us,PandP.10).gifMarkus Says:
Markus It's not good practice to catch all exception types; you should rethrow any unexpected exceptions and not simply swallow them.
exception =>
{
  this.CanSubmit = true;

  // Update unsuccessful, probably due to communication issue with
  // Registration Service. Don't close channel so that we can retry later.
  if (SubscribeToPushNotifications)
  {
    CleanUp();
  }

  if (exception is WebException)
  {
    var webException = exception as WebException;
    var summary = ExceptionHandling.GetSummaryFromWebException(
      "Update notifications", webException);
    var errorText = TaskCompletedSummaryStrings
      .GetDescriptionForResult(summary.Result);
    this.IsSyncing = false;
    this.submitErrorInteractionRequest.Raise(
     new Notification
     { 
       Title = "Push Notification: Server error", 
       Content = errorText 
     }, 
     n => { });
  }
  else
  {
    throw exception;
  }
}

Next Topic | Previous Topic | Home

Last built: May 25, 2012