WinRT vs. Silverlight - Part 7 - Making WebRequests

by Morten 5. October 2011 13:35

See intro blogpost here.

In Silverlight and WPF you are probably used to using WebClient to perform downloads. This is a simple class for doing download/upload string or retrieving a response stream from the server. However, in Windows Runtime this client doesn’t exists. Instead we have new a simple “HttpClient” to do much of what WebClient can. However it works very differently by using the new Task framework, and the more you dive into porting code to WinRT, you will see this a lot, and it will make your life porting code cumbersome. On the upside, the Tasks framework is awesome and it’ll often simplify you code a great deal! I talked a bit about this in my previous post, so please read that first, before you continue here, since this will be building on top of that.

Let’s say we have a method in WPF and Silverlight that uses WebClient to downloada and create an image. Since this is working asynchronously, we would have to either use event handlers or action delegates to return the result and/or the error. The method could look something like this:

public void GetContent(Uri uri, Action<string> complete, Action<Exception> error)
{
    WebClient client = new WebClient();
    client.DownloadStringCompleted += (sender, e) =>
    {
        if (e.Error != null)
            error(e.Error);
        else {
            complete(e.Result);
        }
    };
    client.DownloadStringAsync(uri);
}

 

Here’s how a method like that could look in WinRT:

public async Task<string> GetContentI(Uri uri)
{
   System.Net.Http.HttpClient client = new System.Net.Http.HttpClient();
   var result = await client.GetAsync(uri);
   return result.Content.ReadAsString();
}

Simple right? Well it' gets even simpler when we have to start consuming that method.

From SL and WPF you would consume the method something like this:

GetContent(uri, (text) => {
          TextBlock tb = new TextBlock() { Source = text };
          LayoutRoot.Children.Add(tb);
      },
      (error) => { /*TODO: handle error*/ }
);

And the same using the Task framework:

try {
   TextBlock tb = new TextBlock() { Source = await GetContent(uri) };
   LayoutRoot.Children.Add(tb);
catch { /*TODO: handle error */ }

You tell me what’s more elegant and readable? Smile

My point here is that Tasks are awesome, so rather than trying to reuse your existing code in WinRT, consider rewriting your existing code to use Tasks and it will work much smoother.

There’s lets create a method for downloading a string over the network that works the same way across the board. (I’ll assume you are using the Async Task CTP for Silverlight or WPF for this).

public async Task<string> GetContent(Uri uri)
{
#if NETFX_CORE
    System.Net.Http.HttpClient client = new System.Net.Http.HttpClient();
    var reqResult = await client.GetAsync(uri);
    return reqResult.Content.ReadAsString();
#else
    WebClient client = new WebClient();
    TaskCompletionSource<string> tcs = new TaskCompletionSource<string>();
    client.DownloadStringCompleted += (sender, e) =>
    {
        if (e.Error != null)
            tcs.TrySetException(e.Error);
        else
            tcs.TrySetResult(e.Result);
    };
    client.DownloadStringAsync(uri);
    return await tcs.Task;
#endif
}

Note you could also use the new .NET method “WebClient.DownloadStringTaskAsync” which would simplify the above quite a lot. I used the event based approach to demonstrate how you would handle the cases where you don’t already have a task implementation available.

Tags:

WPF vs Silverlight | Windows Runtime | WPF | Silverlight

WinRT vs. Silverlight - Part 6 - Using Tasks

by Morten 5. October 2011 09:54

See intro blogpost here.

When you start working with WinRT, you start seeing classes missing or changed. A lot of methods are now only asynchronous, like making a web request, opening, reading and writing files etc. They all now on the Task framework, and they do this to ensure you application is always responsive. However without this framework, it would be a pain to code against an API that’s inherently asynchronous all the way through. But with it, it makes it really easy to write code against it.

As an example here’s how you normally write async operations in WPF and Silverlight:

var myObject = new MyClass();
myObject.Completed += (sender,e) => { 
   //Completed event handler 
if(e.Error == null) { statusText.Text = "My Operation has completed: " + e.Result;
} }; myObject.StartAsync(); //Starts process and fires "Completed" event when done

So we first hook up to the event handler, then call start. When you look at the code, the lines are not executed in the order they are written. The completed delegate executes after the operation completes after Start() has been run (even explaining it makes it sound confusing). Also you cannot throw exceptions from an asynchronous running process, because there will be no way to catch it, so you’ll have to parse an exception object back as part of your result and check it.

This pattern makes it hard to read and understand the code, since it’s not immediately obvious what code runs when. Especially when the code gets more complex and/or your eventhandlers are declared elsewhere. When we then nest events, it gets very convoluted quickly:

var myObject = new MyClass();
myObject.Completed += (sender,e) => { 
   if(e.Error == null) {
       //Perform next operation
       var myObject2 = new MyClass();
       myObject2.Completed += (sender2,e2) => { 
           if(e2.Error == null) {
              //Completed event handler
              statusText.Text = "My Operation has completed: " + e2.Result;
} }; myObject2.StartAsync(); } }; myObject.StartAsync();

With the Task framework this becomes very simple and straightforward to write and more importantly read:

var myObject = new MyClass();
try {
     string result = await myObject.StartAsync();
     var myObject2 = new MyClass();
     string result2 = await myObject2.StartAsync();
     statusText.Text = "My Operation has completed: " + result2;
} catch { }

Notice that we don’t bother with checking an error object. We simply use try/catch instead, and the task returns the result up front. If it wasn’t for the little “await” keyword, it looks like this is just good ol’ synchronous programming! This is pure awesomeness!

If you are planning on porting a large application from Silverlight or WPF and it uses a lot of event-based asynchronous programming, you are probably in for some work. Not to say that your application can’t use events, but a lot of the WinRT API’s don’t have eventhandlers any more, so if you insist on keeping it this way, you would have to wrap all the built in tasks into some event-based classes. I would probably rather focus on moving forward and getting this cleaned up. And since the Task framework is already available in .NET 4 and there’s a CTP for Silverlight (and included in upcoming v5!) and WinPhone, you could port your original code to start taking advantage of this, making your code reuse easier and cleaner moving forward.

So how would you go about wrapping an event-based class into a task based method? Well let’s continue using the MyClass example above, and wrap it in a task. Here’s how that would look:

public Task<string> StartAsync()
{
    var tcs = new TaskCompletionSource<string>();
    var obj = new MyClass();
    obj.Completed += (sender,e) => {
        if (e.Error != null)
              tcs.TrySetException(e.Error);
        else
              tcs.TrySetResult(e.Result);
    };
    obj.StartAsync();
    return tcs.Task;
}

Basically we return Task of string, instead of just a string. We use the TaskCompletionSource of string to handle error and result messaging back to the task.

So when should you make a method an asynchronous task? The rule of thumb is: If it takes more than 50ms to execute, it should be asynchronous! This helps a lot towards preventing the UI from becoming choppy and/or unresponsive.

Tags:

Silverlight | Windows Runtime | WPF

About the author

Morten Nielsen

Silverlight MVP

Morten Nielsen
<--That's me
E-mail me Send mail

Twitter @dotMorten 

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

© Copyright 2005-2014

Month List

Recent Comments

Comment RSS