Part 19 - Understanding async and Awaitable Tasks

Play Part 19 - Understanding async and Awaitable Tasks
Sign in to queue

Description

We’ve spent a lot of time examining the Hub App Template, and for good reason — it illustrates many important concepts.  We’ll turn our focus back to the SampleDataSource class to talk about several keywords that I’ve ignored until now.  The keywords I'm referring to are:

(1) async
(2) await
(3) Task<T>

These keywords are newly added to C# 5.0 and are generally referred to as the new "async" feature, which is short for "asynchronous" or "asynchrony".  In a nut shell, this new feature is a simplified way of improving the performance of the app and making it more responsive to the user without the complexity of writing code to use multiple threads.  If you call a method of the Windows Phone API or some other library supporting async that could potentially take a "long time", the method will say "I promise to get those results to you as soon as possible, so go on about your business and I'll let you know when I'm done".  The app can then continue executing, and can even exit out of method contexts.  Another word for this in computer programming terminology is a "promise".

Under the hood, when you compile this source code (i.e., the SampleDataSource class), the Compiler picks apart the source code and implements it as a complex series of statements in the Intermediate Language to allow this to happen flawlessly and it does it without the use of multiple threads in most cases.

Async is best used for operations that have a high degree of latency, but are not compute intensive.  So, for example, the GetSampleDataAsync() method retrieves data from the SampleData.json file and loads it into an object graph of SampleDataGroup and SampleDataItem instances.  That could potentially take a second or two which is an eternity in computing.  Another example would be calling a web API to collect data.  Relying on communications over the Internet could potentially take a long time.  But the Phone's PROCESSOR is not busy at all.  It's just sitting there, waiting for a reply from the web service.  This is known as "I/O Bound" ... I/O means "In / Out" ... so things like the file system, the network, the camera, etc. involve "I/O bound operations" and are good candidates for async.  In fact, the Windows Phone API designers decided to bake async into all I/O Bound operations forcing you to use this to keep the phone responsive to the end user.

Contrast that to a compute-intensive operation such as a photo filter app that must take a large image from the camera and run complex mathematical algorithms to change the colors or the position of each pixel in the image.  That could take a long time, too, but in that case, the Phone's processor is hard at work.  This type of operation is known as "CPU Bound".  This is NOT a good use of async.  In this case, you would want to consider a Background Worker which helps to manage threads on the Windows Phone platform.  If you are developing in .NET, you may prefer to work with threading via the Task Parallel Library instead, or revert back to managing threads manually which has been an option since the very earliest versions of .NET.

Understanding multi-threading, parallel programming, the Task Parallel Library, even Background Workers on the Windows Phone API are WAY beyond the scope of this series and this lesson.  It's a large topic and I'll not lie to you, it makes my head spin. If you want to learn a little more about async and how it applies to the Windows RunTime, check out:

Working with Async Methods in the Windows Runtime

... and be sure to read the comments where I further explain this idea.

But back to the topic at hand ... async is for those common occasions when you have a blocking operation that is not compute intensive, such as our case where we are waiting for these lines of code to complete:

StorageFile file = await StorageFile.GetFileFromApplicationUriAsync(dataUri);

string jsonText;
using (var stream = await file.OpenAsync(Windows.Storage.FileAccessMode.Read)) {
. . . }

In this case, we use the await keyword, which says "I'll get back to you when I'm finished".  The thread of execution can continue on through the other lines of code until it absolutely must have the results of that operation.  Now, in our case, we attempt to open and work with the contents of the file in the very next line of code, so little advantage is gained.  However, we must still use the await keyword because the StorageFile.GetFileFromApplicationUriAsync() and file.OpenAsync() methods both return IAsyncOperation<TResult>.  IAsyncOperation<TResult> is just a specialized version of Task<TResult> that is used by the Windows and Phone API.  By virtue of the fact that they return a Task<T>, we must await the results.  You'll see await-able methods with a common convention ... they all end with the suffix Async.

Furthermore, any method that uses an await-able method must be marked with the async keyword in their method signature, AND if it is supposed to return a value it must return the value wrapped with a Task<T>.  If the method is void, it will merely be marked void in the method signature.  Since both the StorageFile.GetFileFromApplicationUriAsync() and file.OpenAsync() methods are marked with await, and our method, GetSampleDataAsync() returns a Task we must mark our method with async. 

private async Task GetSampleDataAsync() { }

In this case, Task is the return value.  What is returned exactly?  Just a promise that the GetSampleDataAsync() will finish running and when it does, any code calling GetSampleDataAsync() will be notified.  Here again, a lot of compilation magic happens at this point, the likes of which I don’t pretend to fully understand.

How about in the case of GetItemAsync():

public static async Task<SampleDataItem> GetItemAsync(string uniqueId)
{ . . . }

In this case, Task<SampleDataItem> is returned.  But what is returned exactly?  Just a promise that the GetItemAsync() will finish running and when it does, any code calling GetItemAsync will be notified and will be rewarded with an instance of SampleDataItem.  Again, at this point a lot of compilation magic happens.

In both of these cases, the presence of an awaitable task essentially means that any code  that calls our methods can continue executing until it absolutely needs the result of those methods.

Again, remind me, why are we doing all of this?  In hopes of making our app more responsive.  This operation should not be blocking the Phone's processor from taking on other tasks like answering a phone call or running background tasks on behalf of other apps.  It won't prevent the user from selecting any of the hardware buttons on the phone and getting an instant response.

Finally, even if you didn’t understand much of what I just said, let me make it really simple:

(1) Certain methods in the Phone API use awaitable tasks to make ensure that they return control back to your code thus making your app hopefully as responsive as possible.

(2) Therefore, when you need to use one of these methods in your app, you need to do the following:

private void myExample() {
  Uri dataUri = new Uri("ms-appx:///DataModel/SampleData.json");
  StorageFile file =  StorageFile.GetFileFromApplicationUriAsync(dataUri);
}

(2a) Add await before the call to the Async method
(2b) Remove void, add: async Task

private async Task myExample() {
  Uri dataUri = new Uri("ms-appx:///DataModel/SampleData.json");
  StorageFile file = await StorageFile.GetFileFromApplicationUriAsync(dataUri);
}

(2c) Call your new method using await

… Or, if you intend to return a value …

private StorageFile myExample() {
    Uri dataUri = new Uri("ms-appx:///DataModel/SampleData.json");
  StorageFile file =  StorageFile.GetFileFromApplicationUriAsync(dataUri);
}

Follow the previous steps, but do this instead:

(2d) Rework the return type to:  async Task<StorageFile>

So:

private async Task<StorageFile> myExample() {
    Uri dataUri = new Uri("ms-appx:///DataModel/SampleData.json");
  StorageFile file = await StorageFile.GetFileFromApplicationUriAsync(dataUri);
}

… and the compiler will take care of the rest.

Recap

To recap, the big take away in this lesson is what async is, how it works, what scenarios it was designed to address, and it's overall purpose -- to keep the phone and your apps responsive during long running I/O bound operations.


Embed

Download

Download this episode

The Discussion

  • User profile image
    arko

    Being accustomed to threads these async-awaits make my head spin.

    In phone 8.0 I used to have something like:

    Private Sub MainPage_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
      Dim theThread As New System.Threading.Thread(AddressOf DoSlowWork)
      theThread.IsBackground = True
      theThread.Start(New MyParametersBundledClass With {.FileToOpen = "MyDir/MyFile.zip"})
      'Other stuff that needs to run right away before the thread above finishes!
    End Sub
    
    Private Sub DoSlowWork(ParameterBundle As MyParametersBundledClass)
      Dim fs As IO.FileStream = IO.File.OpenRead(ParameterBundle.FileToOpen)
      'Some processing here to populate MyParameters As MyParametersClass...
      Deployment.Current.Dispatcher.BeginInvoke(New Action(Of MyParametersClass)(AddressOf UpdateViewModel), MyParameters)
    End Sub
    Private Sub UpdateViewModel(NewStuff As MyParametersClass))
      MyViewModel.MyObservableCollection.Add(NewStuff.NewItems)
    End Sub

    Now that in phone 8.1 the OpenRead is awaitable I have a feeling I should be doing more changes than just adding Async to all the Subs all the way till MainPage_Loaded; But I have no idea what?

    Dim fs As System.IO.Stream = Await Windows.Storage.ApplicationData.Current.LocalFolder.OpenStreamForRead(ZipPath)

     

  • User profile image
    ecoderie

    Thanks Bob, you made it really easy for me to understand this concept. I learnt a lot from you on channel9 and learnvs.net(devu). you're doing great.

Add Your 2 Cents