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.
Comments
Anonymous
November 03, 2010
Nice work! Easy to understand, implemented it in just a few minutes.Anonymous
November 03, 2010
I used you extension method, to wait for all the data for the comboboxes is loaded. leeontech.wordpress.com/.../ria-services-and-combobox-lookups-with-async-ctpAnonymous
November 03, 2010
Awesome. Glad it helps.Anonymous
November 03, 2010
I don't understand... what does this give you that you can't already do with threads? Implicit use of pool threads?Anonymous
November 03, 2010
It's not really about threading (it can be, but everything running here is UI thread affinitized). It's about the async pattern. The patterns above are concise and robust in ways that are difficult to achieve now. For instance, in the serial case we're using three await statements. To achieve the same behavior with the current bits you would have to call the first load, wait for the callback, call the second load, wait for a callback, call the third load, wait for a callback, and then do something. In the parallel case we're running three async calls at the same time and waiting until they're all complete before doing something. Again, it's a difficult (or at least verbose) task with the current bits.Anonymous
December 22, 2010
Good article! would you please attach silverlight solution?Anonymous
January 06, 2011
There's not a whole lot more to it. Just create a new Silverlight Application, Add a DomainService, and modify the MainPage. Also, make sure both your projects reference the appropriate version of the AsyncCtpLibrary shipped with the CTP.Anonymous
January 09, 2011
Oren Beeri post a little bit simplified version with source code here : zormim.wordpress.com/.../using-the-new-async-framework-with-riaAnonymous
June 20, 2011
I couldn't understand a small part of it. How do you implement this method? private async Task<RGB[]> GetEntitiesAsync() {…}Anonymous
June 20, 2011
@Zia I'd recommend taking a look at this link (msdn.microsoft.com/.../async.aspx) and going through some of the walkthroughs. The purpose of the API above was just to show how to consume an API that implemented the async pattern from a DomainService.Anonymous
September 29, 2011
This works great went return entities or collection of entities. But now I have a method in the server the returns a string and it does not work. I get en error "I tried that and now I get Cannot implicitly convert type 'System.ServiceModel.DomainServices.Client.InvokeOperation<string>' to 'string' Any thoughts?Anonymous
September 29, 2011
@Paul The type you get back is Task<InvokeOperation<T>>. You can get the string using task.Result.Value.Anonymous
December 12, 2011
Thanks Kyle, excellent article. This all worked like a charm in my own project, until I upgraded my project to Silverlight 5 using all the latest bits from the recent SL5 release, and the AsyncCtpLibrary_Silverlight5.dll from the CTP3 package. It all compiled and ran after the upgrade, but no data was returned. Code was unchanged. When trying to revert to Silverlight 4, the compiler no longer recognizes await and async .... Anyone tried this successfully?Anonymous
December 13, 2011
@Lars Can you put together something from scratch using SL5 and Async? I'd assume so and that something went wrong in the upgrade. Thanks goodness for source control (hopefully :S). I'd be interested to know if you couldn't.Anonymous
November 12, 2014
Exactly what I needed !