Using the Visual Studio Async CTP with RIA Services

I’ve been excited about this CTP for quite a while now and it’s great to finally have a chance to play with it. Way back in early RIA days we had a number of conversations with the TPL (Task Parallel Library) folks to see if we could use Task<T> with RIA. We were never quite able to sort out the details so now that I’ve finally had a chance to use the two together I’m pretty excited.

Using Async on the Server

As soon as you pick up the Async CTP, you get the idea it can be used everywhere. Asynchronous code becomes just as easy to write as synchronous code, and  the performance improvements it offers are tantalizing . It’s all still pretty new, but I’m guessing you’ll find reasons to use it in your DomainServices pretty quickly.

Right now, all DomainService operations are synchronous, so it’s up to you to marry the two worlds. Luckily, Task makes this pretty easy to accomplish. For instance, let’s say you want to use an asynchronous method that returns a Task. To consume it synchronously, all you have to do is write a synchronous method that Waits for the task to complete.

   using RGB =
    System.Tuple<SampleRedEntity, SampleGreenEntity, SampleBlueEntity>;

  [EnableClientAccess()]
  public class SampleDomainService : DomainService
  {
    public IEnumerable<SampleRedEntity> GetRedEntities()
    {
      return this.GetEntitiesSync().Select(t => t.Item1);
    }

    public IEnumerable<SampleGreenEntity> GetGreenEntities()
    {
      return this.GetEntitiesSync().Select(t => t.Item2);
    }

    public IEnumerable<SampleBlueEntity> GetBlueEntities()
    {
      return this.GetEntitiesSync().Select(t => t.Item3);
    }

    private RGB[] GetEntitiesSync()
    {
      Task<RGB[]> task = this.GetEntitiesAsync();
      task.Wait();
      return task.Result;
    }

    private async Task<RGB[]> GetEntitiesAsync() {…}
  }

Using Async in Silverlight

In Silverlight, using Async becomes a little more interesting for two reasons; (1) You’re already forced to use async, and (2) RIA Services provides an async model similar to Task<T> .

My first step in integrating the CTP with RIA was to create a simple extension method to consume our RIA operations (LoadOperation<T> , etc.) as Tasks. I played around with different variations, but in the end it turned out to be quite simple.

   public static class OperationExtensions
  {
    public static Task<T> AsTask<T>(this T operation)
      where T : OperationBase
    {
      TaskCompletionSource<T> tcs =
        new TaskCompletionSource<T>(operation.UserState);

      operation.Completed += (sender, e) =>
      {
        if (operation.HasError && !operation.IsErrorHandled)
        {
          tcs.TrySetException(operation.Error);
          operation.MarkErrorAsHandled();
        }
        else if (operation.IsCanceled)
        {
          tcs.TrySetCanceled();
        }
        else
        {
          tcs.TrySetResult(operation);
        }
      };

      return tcs.Task;
    }
  }

To give some context on the next part, here’s the UI I’m using. It’s just a few DataGrids bound to the EntitySets on my DomainContext. I’ve also included a BusyIndicator to spin while I’m loading everything.

   <UserControl …>

    <UserControl.Resources>
      <web:SampleDomainContext x:Key="sampleDomainContext" />
    </UserControl.Resources>
    
    <Grid x:Name="LayoutRoot" Background="White">
      <toolkit:BusyIndicator Name="busyIndicator">
        <StackPanel>

          <TextBlock Text="Reds" Margin="2" FontWeight="Bold"/>

          <sdk:DataGrid Name="redsDataGrid" IsReadOnly="True"
            ItemsSource="{Binding SampleRedEntities,
                          Source={StaticResource sampleDomainContext}}"/>

          <TextBlock Text="Greens" Margin="2" FontWeight="Bold"/>

          <sdk:DataGrid Name="greensDataGrid" IsReadOnly="True"
            ItemsSource="{Binding SampleGreenEntities,
                          Source={StaticResource sampleDomainContext}}"/>

          <TextBlock Text="Blues" Margin="2" FontWeight="Bold"/>

          <sdk:DataGrid Name="bluesDataGrid" IsReadOnly="True"
            ItemsSource="{Binding SampleBlueEntities,
                          Source={StaticResource sampleDomainContext}}"/>

        </StackPanel>
      </toolkit:BusyIndicator>
    </Grid>
  </UserControl>

Finally, here’s where I get to do all the neat stuff.

   public partial class MainPage : UserControl
  {
    private readonly SampleDomainContext _context;

    public MainPage()
    {
      InitializeComponent(); ;

      this._context =
       (SampleDomainContext)this.Resources["sampleDomainContext"];

      this.InitializeData();
    }

    private async void InitializeData()
    {
      this.busyIndicator.IsBusy = true;

      // Serial
      await this._context.Load(
              this._context.GetRedEntitiesQuery()).AsTask();
      await this._context.Load(
              this._context.GetGreenEntitiesQuery()).AsTask();
      await this._context.Load(
              this._context.GetBlueEntitiesQuery()).AsTask();

      // Parallel
      await TaskEx.WhenAll(
        this._context.Load(this._context.GetRedEntitiesQuery()).AsTask(),
        this._context.Load(this._context.GetGreenEntitiesQuery()).AsTask(),
        this._context.Load(this._context.GetBlueEntitiesQuery()).AsTask()
        );

      this.busyIndicator.IsBusy = false;
    }
  }

I’ve made the InitializeData method async so it starts to run in the constructor, but finishes well after. As you can see, I set the busy indicator to busy before loading data and then ,when all the data is successfully loaded, I set it to idle again. Also, I’ve shown two approaches for invoking multiple queries. First, I show how Loads can be invoked serially such that each is only started after the previous one completes. Next, I show how all the Loads can be run in parallel. The WhenAll method will ensure all the loads have completed successfully before I set my busy indicator to idle.

This is just a small sample of the things you can do with the Async CTP, but I’m already a big fan. Hopefully you find this helpful, and please let me know if you have any questions.