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)

Using async and await in Silverlight 5 and .NET 4 in Visual Studio 11 with the async targeting pack

Pete Brown - 22 May 2012

Last September, I introduced the idea of Tasks in Silverlight. One of the things I really like about .NET 4.5, and the C# compiler that comes with it, is the ability to simplify asynchronous code by using the async modifier and await operator. Just yesterday, I posted a bit on how these two keywords are used by the compiler to take care of all the heavy lifting of asynchronous code.

The key thing to note here is that the work is done by the compiler, not the runtime. That means that if you can use the latest C# compiler, you can take advantage of this new feature. A grasp of the async pattern and these two keywords will also help prepare you for Windows Metro development. Note that Silverlight doesn't have the same "asynchronous everywhere" set of APIs that you'll find in WinRT or .NET 4.5 so async and await won't be quite as useful here as they are there, but there's some help for that too (read on). You can still create your own async methods, however, which is especially useful if you plan to share code with a WinRT XAML application.

I'll focus on Silverlight 5 here, but understand that this works with .NET 4 in VS11 as well. Note also that I'm using Visual Studio 11 beta for this post, so final details may change when VS11 is released. Also, Visual Studio 11 and the compiler which supports async/await requires Windows 7 or Windows 8. This will not work on Windows XP or a Commodore 128.

In Visual Studio, create a new Silverlight application.

image

Choose all the normal options in the project creation dialog (yes, create a new site, no on RIA)

For the MainPage.xaml, add a single button and wire up an event handler.

<UserControl x:Class="SilverlightAsyncDemo.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">

<Grid x:Name="LayoutRoot" Background="White">
<Button x:Name="TestAsync"
Content="Press Me!"
FontSize="25"
Width="200"
Height="75"
Click="TestAsync_Click" />
</Grid>
</UserControl>

The next step is to add the Async Targeting Pack. I'll use NuGet to grab it.

image

Pick your favorite way to use NuGet. Some prefer the console. I'm going to use the project's context menu and select "Manage NuGet Packages". From there, I'll search for AsyncTargetingPack.

image

Once you see it, click "Install", accept the terms (maybe even read them first), then close the NuGet package manager. You'll notice that the Silverlight project now includes a reference to the Microsoft.CompilerServices.AsyncTargetingPack Silverlight 5 library.

A simple example using await

If you have a call which uses the async callback pattern, such as with a WebClient in Silverlight, using await is pretty easy, due to the built-in extension methods:

private async void TestAsync_Click(object sender, RoutedEventArgs e)
{
var uri = new Uri("http://localhost:10697/SilverlightAsyncDemoTestPage.html");

var client = new WebClient();

var results = await client.DownloadStringTaskAsync(uri);

Debug.WriteLine(results);
}

Note that the event handler must be marked as async in order to use the await keyword.

These extension methods, DownloadStringTaskAsync in this example, are included in the AsyncCompatLibExtensions installed with the async targeting pack.

image

The extension methods mimic many of the same async functions already available in .NET 4.5. That's great for compatibility and to help reduce how many different approaches you need to learn.

Give me something to wait on

"Hey teacher! I've got my pencil! Now give me something to write on." - Pre-geriatric David Lee Roth

Let's do something a little more typical. In this case, I'm going to create a web service that we'll use asynchronously.

In the web project, create a "Services" folder and in that, create a new Silverlight-Enabled Web Service named CustomerService.

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

namespace SilverlightAsyncDemo.Web.Services
{
[ServiceContract(Namespace = "uri:demo.silverlight.async")]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class CustomerService
{
[OperationContract]
public List<Customer> GetCustomers()
{
string[] firstNames = new string [] {"Pete", "Joe", "Ima", "John", "Jim", "Scott", "George", "Frank", "Al", "Jeremy", "Jason", "Alex", "Bob", "Bill", "William", "Severus", "Johnny", "Albus", "Han", "Harry", "Chris", "Christine", "Kristen", "Amy", "Leia", "Erin", "Heather", "Melissa", "Abby", "Ben", "Alice", "Morgan", "Chip"};
string[] lastNames = new string [] {"Brown", "Jones", "Braxton", "Huxley", "Solo", "Organa", "Vader", "Skywalker", "Potter", "Dumbledore", "Snape", "Avatar", "Cranks", "Bravo", "Grime", "Gee", "A.", "B.", "C.", "D.", "E.", "Z.", "X.", "Walker", "Franklin", "Moore", "Less"};

Random rnd = new Random();
const int CustomerCount = 200;

var customers = new List<Customer>();

// generate a bunch of dummy customer data
for (int i = 0; i < CustomerCount; i ++)
{
int firstIndex = rnd.Next(0, firstNames.Length-1);
int lastIndex = rnd.Next(0, lastNames.Length-1);

customers.Add(
new Customer()
{
FirstName = firstNames[firstIndex],
LastName = lastNames[lastIndex]
});
}

return customers;

}
}

public class Customer
{
public string LastName { get; set; }
public string FirstName { get; set; }
}
}

Build the solution, and then add a service reference from the Silverlight client. When creating the service reference, name the namespace "Services". But before you leave this dialog, we have one VERY important thing to check. Click the "Advanced" button

image

Foiled! We can't generate task-based service references. Why? Well, Silverlight didn't have the necessary support until the async targeting pack. So, we'll have to do this manually. If you were using .NET 4, you could simply generate Task-based operations and be done with it.

NOTE

If we were using RESTful calls, the "add service reference" stuff doesn't come into play at all - it's all just POST/GET/DELETE/PUT/PATCH with a URL. SOAP, which is what many folks use behind the firewall, is more difficult to work with, so the service reference helps. I'm a big fan of the ASP.NET Web API and RESTful services. You should check them out.

Creating an async-friendly wrapper for the service reference

Making your service method awaitable certainly makes it easier to consume by other developers, and easier to work into your own application workflow (especially if you have service calls that rely on the results of other service calls). The code to make it awaitable is not very complicated at all. However, I like to put this type of code into a separate service proxy or client class in the Silverlight project.

I created a new project folder named "Services" in the Silverlight project. In that, I added a new class named "CustomerServiceProxy". The code for the proxy is as follows:

using System.Collections.ObjectModel;
using System.Threading.Tasks;

namespace SilverlightAsyncDemo.Services
{
public class CustomerServiceProxy
{
public static Task<ObservableCollection<Customer>> GetCustomers()
{
var tcs = new TaskCompletionSource<ObservableCollection<Customer>>();

var client = new CustomerServiceClient();

client.GetCustomersCompleted += (s,e) =>
{
if (e.Error != null)
tcs.TrySetException(e.Error);
else if (e.Cancelled)
tcs.TrySetCanceled();
else
tcs.TrySetResult(e.Result);
};

client.GetCustomersAsync();

return tcs.Task;
}
}
}

The code for this was adapted from the Task-based Asynchronous Pattern whitepaper listed under "Additional Information" below. The task is set up to return an ObservableCollection of Customers, which is (by default) what the service reference code generation created for me in the CustomerServiceClient class. (I also love how we managed to get both spellings of Canceled into the same method O_o.)

Once you have the proxy created, it may be used from the ViewModel, or any other code. Here it is in the button-click code on MainPage. As before, the event handler has been marked with the async modifier in order to support using the await operator.

private async void TestAsync_Click(object sender, RoutedEventArgs e)
{
var customers = await CustomerServiceProxy.GetCustomers();

foreach (Customer c in customers)
{
Debug.WriteLine(c.FirstName + " " + c.LastName);
}
}

This can serve as a good starting point for your own code. Not only will it make your async code easier to work with when using Visual Studio 11, but it will also help you bridge towards and share code with .NET 4.5 WPF, and to Metro style XAML applications.

Additional Information

         
posted by Pete Brown on Tuesday, May 22, 2012
filed under:          

22 comments for “Using async and await in Silverlight 5 and .NET 4 in Visual Studio 11 with the async targeting pack”

  1. Petesays:
    @Swaroop

    You can't use async and await with Silverlight and VS2010. The Async CTP (as I recall) also wasn't compatible with the release of Silverlight 5, but I didn't keep up with their later updates. We have Task<T> built in to SL5, but not the new C# compiler features.

    Pete
  2. Swaroopsays:
    Hi Pete,

    I am using Visual Studio 2010 with .NET 4, Silverlight 5 and Async CTP. I am able to access async and await keywords thru AsyncCtpLibrary_Silverlight5.dll. I am trying to add a service reference, and I want to Generate Task Based Operations for this service. But, I am not able to see the option "Generate task based operations" as mentioned in your screenshot. Is there a way to generate Task based operations for the service I am referencing? Or do I have to create them manually?

    Thanks.
  3. Petesays:
    @Swaroop

    Not that I know of. Those are features of the new "Add Service Reference" code in VS11.

    Also note that the Async CTP, *as I understand it* is not something that will be maintained beyond a CTP. I believe it's just a helper for now as the real functionality required some compiler support and was baked into the next rev of the compilers and tools. Truthfully, I wasn't aware that it was updated beyond Silverlight 5 beta (the RC didn't work correctly with the Async CTP). Glad to see it is working.

    Pete
  4. Nairsays:
    Quick question, I was following the example in VS2012 on SL5 and everything was compiling fine till I added CustomerServiceProxy, at this time, the compiler is complaining, it does not know anything about the customer class. Just curious, am I missing anything?
    Thanks
  5. Sylvainsays:
    Hi!
    Great post, nice to see that these extension methods exists! :)

    One thought about their performance:

    Using dotNet4.5, ReadAsync(...) methods are really clever: if I call ReadAsync(...) on an IO stream, then IO completion ports will be used first, and a new thread will be created (in fact a ThreadPool one) only when IO is finised. Great!
    Even greater, due to the design of Tasks, if I call ReadAsync on a custom stream that is based on an IO stream, this will work too! My custom stream calls ReadAsync on the underlying IO stream, and no new thread is created before IO is completed... That's just brilliant... :)

    However, under Silverlight/dotNet4.0, the extension method cannot do such thing: the best it can do is to call BeginRead(...) on my custom thread, and this call is synchronous! So I assume that the extension methods, in order to be really asynchronous, do create a new thread (use a threadpool one) straight away.

    That's no big deal, because this very first thread will have very little to do (call the IO completion BeginRead on the underlying stream) before being available in the threadpool again...
    That's no big deal also because this will occur on client side, while it's on server side, where you can use dotNet4.5, that you really care about sparing threads...

    I just wanted to know if you share my analysis... :)

    Sylvain.
  6. Irvingsays:
    For me it's not possible, I don't get how you managed to do it. When I use async await it blocks the UI thread and is not possible to make a call to a WCF service
  7. Irvingsays:
    Oh! sorry man I got it working, results I must use async from the event of the UI, that is kind of hard for an app that is already build with many things working. Great post!
  8. CBsays:
    I'm trying to use your simplest example in an MVVM SL5 app in VS2012.
    I used NuGet to get the AsyncTargetingPack.
    In the Action for the RelayCommand I copied your code for TestAsync_Click, adjusted the URI, added some credentials and ran it.
    Unfortunately, I'm getting a "System.NotSupported" exception on the call to DownloadTaskStringAsync().
    Any ideas on what is going wrong?
    Thanks --
  9. Jerrysays:
    Hey Pete, I started reading your SL 5 in Action book on safari online and loved it so far! I discovered your code in some of the later chapters on using client code to call Web API services async. I used a lot of anonymous methods in the calls but I have two issues that seems difficault to figure out one is handling errors on the return of the async calls and the other is memory leaks big time. I am using VS2010 and SL5 and the 4.0 framework. I was just wondering since I used what seems to be your method of async calling from your book if you should shed some light on memory leaks for anonymous methods. Also, I started reading your book cover to cover but not sure how long it will take. I was just wondering now should i or could i just rewrite my async calls using 4.5 and VS2012 with this async targeting pack? One thing that seems to complicate things are i have a few things that need to be done in a synchronious manner so i chained a lot of async calls. Do you have good white papers or chapters on how to use this async targeting pack in a production environment? Really seems like we need a client API for calling Web API maybe this async targeting pack is it... I can provide code sample if need but here is one of my more basic ones...

    private bool Login(string userName, string password)
    {
    bool successful = true;
    MasterContainer = null;
    byte[] bytes = null;

    string jsonUser = "{\"UserName\":\"" + userName + "\",\"Password\":\"" + password + "\"}";
    bytes = Encoding.UTF8.GetBytes(@jsonUser);

    UserDisplayName = userName.ToUpper();

    string url = BaseAddress + "Login";
    WebRequest.RegisterPrefix("https://", WebRequestCreator.ClientHttp);
    WebRequest.RegisterPrefix("http://", WebRequestCreator.ClientHttp);

    MasterContainer = new CookieContainer();
    HttpWebRequest webRequest = null;
    webRequest = (HttpWebRequest)WebRequest.Create(url);
    webRequest.ContentType = "application/json";
    webRequest.Method = "POST";
    webRequest.ContentLength = bytes.Length;
    webRequest.CookieContainer = MasterContainer;
    webRequest.BeginGetRequestStream((result) =>
    {
    HttpWebRequest request = (HttpWebRequest)result.AsyncState;
    Stream postStream = request.EndGetRequestStream(result);
    postStream.Write(bytes, 0, bytes.Length);
    postStream.Close();
    request.BeginGetResponse((requestResult) =>
    {
    HttpWebRequest request2 = null;
    HttpWebResponse response = null;
    Stream streamResponse = null;
    try
    {
    request2 = (HttpWebRequest)requestResult.AsyncState;
    response = (HttpWebResponse)request2.EndGetResponse(requestResult);
    streamResponse = response.GetResponseStream();

    if (response.StatusCode != HttpStatusCode.OK)
    {
    successful = false;
    InspectionGridVisibility = false;
    Deployment.Current.Dispatcher.BeginInvoke(delegate
    {
    MessageView mv = new MessageView("Sign In failed. Please try again." + "\nStatus Code: " + response.StatusCode.ToString());
    mv.Show();
    });
    return;
    }

    List<string> roleList = new List<string>();
    var returnJson = (JsonObject)JsonArray.Load(streamResponse);
    //UserDisplayName = returnJson["DisplayName"];
    JsonArray roles = (JsonArray)returnJson["Roles"];
    if (roles.Count == 0)
    {
    successful = false;
    InspectionGridVisibility = false;
    Deployment.Current.Dispatcher.BeginInvoke(delegate
    {
    MessageView mv = new MessageView("Sign In failed. Please try again." + "\nRoles are zero.");
    mv.Show();
    });
    return;
    }

    //Note: There may be a better way to serialize this Json data. I could not get the
    // DataContractJsonSerializer to work for me right now!
    foreach (var r in roles)
    {
    string rol = r.ToString().ToLower();
    rol = rol.Replace('\\', ' ');
    rol = rol.Replace('\"', ' ');
    rol = rol.Trim();
    roleList.Add(rol);
    }

    Deployment.Current.Dispatcher.BeginInvoke(delegate
    {
    Roles = roleList;
    IsAdmin = false;
    IsReadOnlyMode = true;
    foreach (var r in roleList) //Only admin and inspectors can update data!
    {
    //Put Highest role first. Admin is the highest level.
    if (r == "admin")
    {
    IsAdmin = true;
    IsReadOnlyMode = false;
    }
    //If user is an inspector and an admin then if inspect logic
    //is first they would never be able to use admin functions!
    if (r == "inspector")
    {
    IsReadOnlyMode = false;
    }
    //All other roles for now are read only. Add other roles here if needed.
    }

    InspectionGridVisibility = true;
    SignInIsEnabled = false;
    LoadListOfRunNames();
    });

    //CheckUIAccess.Invoke(() => display.Text = responseString);
    }
    catch(Exception ex)
    {
    successful = false;
    InspectionGridVisibility = false;
    Deployment.Current.Dispatcher.BeginInvoke(delegate
    {
    MessageView mv = new MessageView("Sign In failed. Please try again." + "\nAn Exception Occurred.");
    //+ "\nException: " + ex.Message + "\nStack: " + ex.StackTrace);
    var ex1 = ex;
    mv.Show();
    });
    }
    finally
    {
    if (streamResponse != null) streamResponse.Close();
    if (response != null)
    {
    response.Close();
    MasterContainer.Add(new Uri(BaseAddress + "Login"), response.Cookies);
    }
    }
    }, request);
    }
    , webRequest);

    if (successful) IsControlBarEnabled = true;
    return successful;
    }
  10. Petesays:
    @Jerry

    Thanks for the kind words.

    The async targeting pack is useful for sure. It'll also help you when/if you decide to create Windows 8+ or WPF 4.5+ apps in the future, as async/await are key there.

    I haven't seen any good Silverlight-specific examples or whitepapers. However, much of the .NET 4.5 code would look similar.

    Anonymous methods and leaks: this is generally only a problem when the anonymous method is wired up to an event handler which is causing the page to stick around. In general, this is also true if you don't unwire event handlers yourself (something most developers don't realize they should do). If you don't want to use anonymous methods, it's easy to create a separate method which can then be both += on the event handler and when done -= to clean it up.

    Pete
  11. sheirsays:
    Hi,
    Using SL5 and instead of using the Nuget package, I have copied just the required DLLS
    Microsoft.Threading.Tasks
    Microsoft.Threading.Tasks.Extensions
    Microsoft.Threading.Tasks.Extensions.Silverlight
    System.IO
    System.Runtime
    System.Threading.Tasks

    The solution builds fine in VS2012.

    But on the build server that uses MSBUILD, my project fails with ..
    ViewModels\AddingServiceAssignmentsViewModel.cs(364,23): error CS1519: Invalid token 'void' in class, struct, or interface member declaration

    That line is where I used the async keyword on a method such as:
    private async void LoadGridResult(SearchLocalAndOrgResponse data)
    { blab blab blab }

    I had tweaked the .csproj file and added the following:
    <PropertyGroup>
    <SLAsyncAwaitBuildResources>..\..\..\Common\SharedLib\Microsoft.Bcl.Build\1.0.14\tools\</SLAsyncAwaitBuildResources>
    </PropertyGroup>


    <Import Project="$(SLAsyncAwaitBuildResources)Microsoft.Bcl.Build.targets" Condition="Exists('$(SLAsyncAwaitBuildResources)Microsoft.Bcl.Build.targets')" />
    <Target Name="EnsureBclBuildImported" BeforeTargets="BeforeBuild" Condition="'$(BclBuildImported)' == ''">
    <Error Condition="!Exists('$(SLAsyncAwaitBuildResources)Microsoft.Bcl.Build.targets')" Text="This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=317567." HelpKeyword="BCLBUILD2001" />
    <Error Condition="Exists('$(SLAsyncAwaitBuildResources)Microsoft.Bcl.Build.targets')" Text="The build restored NuGet packages. Build the project again to include these packages in the build. For more information, see http://go.microsoft.com/fwlink/?LinkID=317568." HelpKeyword="BCLBUILD2002" />
    </Target>


    Any help in fixing the CI build??



Comment on this Post

Remember me