(X) Hide this
    • Login
    • Join
      • Generate New Image
        By clicking 'Register' you accept the terms of use .

Windows 8 Metro: Asynchrony made easy

(6 votes)
Andrea Boschin
>
Andrea Boschin
Joined Nov 17, 2009
Articles:   91
Comments:   9
More Articles
3 comments   /   posted on Jun 04, 2012
Categories:   Windows 8 , General
Tweet

After the birth of Silverlight the developers have had to deal with asynchronous programming much more than before. The responsiveness of the user interface has always been an important matter and asynchronous programming has been the right response also before than Silverlight, in Windows Forms and WPF. But for the first time, Silverlight made a strict request by the framework to run a lot of tasks asynchronous. Due to architectural choices, all the calls to the network API had to be made in an async way, and this caused a number of headaches to developers that for the very first time couldn't forget about asynchronicity paying the price of a bad user experience.

In metro-style applications, the concept of "make it asynchronous" is exploded to the extreme consequences. WinRT, the underlying wrapper for Windows API, request to run asynchronously most of the tasks, with the rule that, if it may run longer than 50 milliseconds, then it has to be asynchronous. Calling the network, using a device, grabbing a photo from the camera, all these actions are asynchronous in metro-style applications but, async is hidden also behind simpler tasks like opening a message box.

All this surfeit of asynchronous, promises to make programmer's life a hell. Luckily, the .NET framework 4.5 introduces a set of tools to the rescue, making asynchronous simpler as drinking a glass of water.

The pitfall of asynchronous programming

Just before diving in the new asynchronous model in metro-style applications, it is required we make a step behind and remember why asynchronous is so boring to people. The problem here is matter of context. When you call a synchronous task, all your code runs in a unitary context so, after the task ended, you are in the same context and you can continue the work without any trouble. Differently, an asynchronous task requires a context switch, from a base flow that continues to run, to another flow for the spanned task. So when the asynchronous task ends, your original context has been lost. Let me make a practical example. Let say you have to call a web service to get the list of products for a specific season, and with the result you have to fill an existing Catalog instance; in a synchronous world you will write this code:

   1: private void FillCatalog(Catalog myCatalog, string season)
   2: {
   3:     WebService service = new WebService();
   4:     IEnumerable<Product> products = service.GetProductsBySeason(season);
   5:     myCatalog.Products = products.ToList();
   6: }

But what if the call to the network is to be made asynchronously, like in metro-style applications? In this case, when you run the GetProductsBySeason you cannot get the list immediately, but your code don't stop and when you reach the line number 4) you get a NullReferenceException because products is still null. To deal with async you have to radically change the approach:

   1: private void FillCatalog(Catalog myCatalog, string season)
   2: {
   3:     WebService service = new WebService();
   4:     service.GetProductsBySeasonCompleted += service_GetProductsBySeasonCompleted;
   5:     service.GetProductsBySeasonAsync(season, myCatalog);
   6: }
   7:  
   8: private void service_GetProductsBySeasonCompleted(object sender, GetProductsBySeasonResultEventArgs e)
   9: {
  10:     Catalog myCatalog = e.UserData as Catalog;
  11:  
  12:     if (myCatalog != null)
  13:         myCatalog.Products = e.Products.ToList();
  14: }

So, the code now is much more complex and, at a fine observer it does not handle the problem completely. The solution is to catch an event raised when the asynchronous task ends and fill the myCatalog instance passed along the service call in the userdata parameter. The call now does not raises any exception but the problem is only moved upper one level because the caller of the FillCatalog method have to deal with asynchronicity in some way. If it expect the catalog instance is ready after the call to this method it mades a wrong assumption and the flow goes wrong.

At an higher level, the real problem when dealing with asynchronous programming is that you have to focus on the asynchronicity matter much more than how you focus on your business problem. And this slow down your work and forces you to architectural choices that makes your code hard to write, debug and maintain.

Tasks to the rescue

The .NET framework 4.0 introduced the new Task Parallel Library (TPL) that is tailored to give to developers more control on asynchronous programming, with a new set of tools that wraps threading in an innovative way. It becomes really strighforward to start threads and deal with problems that are strictly related with threading, like pools, cancellation, task composition and other strangenesses. At the base of the TPL there is the Task class that opens to a wide set of facilities. Here is a short collection of examples that involves the Task class:

   1: // wait a delay
   2: Task.Delay(100);
   3:  
   4: // start a thread to compute something
   5: Task.Run<int[]>(() => CalculatePrimesUpTo(1000));
   6:  
   7: // compose two threads passing to the second the result from the first
   8: Task.Run<int[]>(() => CalculatePrimesUpTo(1000))
   9:     .ContinueWith(t => ReverseSequence(t.Result));
  10:  
  11: // execute two threads and wait both for end
  12: Task[] tasks1 = new Task[] { GetProducts(), GetCategories() };
  13: Task.WhenAll(tasks1);
  14:  
  15: // execute two threads and wait both for end
  16: Task[] tasks2 = new Task[] { RollDice(1), RollDice(2) };
  17: Task.WhenAny(tasks2);

These are only few of the capabilities of this library but it gives an awesome example. What you should have observed is that the result of each Task method is again another Task. This may seems to be inconclusive and definitely does not pose a real solution to the asyncronicity problem. Once you get a Task instance you have to apply the known pattern to get the result.

Starting with .NET Framework 4.5, the C# compiler embraces the TPL deeply, giving you the missing keystone. The new compiler introduces a couple of keywords, async and await that do all the boring work for you. The trick is simple, inside a method you declare "async" you can call the methods that returns Task using "await". Here is an example:

   1: private async void Button_Click(object sender, RoutedEventArgs e)
   2: {
   3:     // start a thread to compute something
   4:     int[] primes = await Task.Run<int[]>(() => CalculatePrimesUpTo(1000));
   5:  
   6:     // do what you want with resulting array
   7: }

When you deal with every async method in metro, the methods that have the "Async" suffix in the name, you will find that each returns an instance of Task. This implicitly enable you to call them using the async-await pattern. If the WebService class in previous section is the instance of the generated proxy you can for sure write the following code:

   1: private async void FillCatalog(Catalog myCatalog, string season)
   2: {
   3:     WebService service = new WebService();
   4:     IEnumerable<Product> products = await service.GetProductsBySeasonAsync(season);
   5:     myCatalog.Products = products.ToList();
   6: }

The trick is made by the "async" keyword that instructs the compiler to expect that something in this method will be asynchronous. Then the "await" keyword, that you can use only inside an async method, ask to the compiler to threat this call in a special way. All this is simply a syntactical sugar as other facilities of the compiler. Under the hood it handles the call doing the dirty work on behalf of you. The code compiled by C# will be something of near to this:

   1: private void FillCatalog(Catalog myCatalog, string season)
   2: {
   3:     WebService service = new WebService();
   4:  
   5:     // the first thread
   6:     Task<IEnumerable<Product>> task = service.GetProductsBySeasonAsync(season);
   7:  
   8:     // the continuation thread
   9:     Task continuation = task.ContinueWith(t => myCatalog.Products = t.Result.ToList());
  10:  
  11:     // wait the work to end
  12:     Task.WaitAny(continuation);
  13: }

Please take note that the last code is not really the code resulting by a compilation but a semplification made by me, just to explain how it work. As you see the part of code after the "await" becomes the task continuation so it is called after the first task ends. All this is completely transparent to the developer that has only the requirement of correctly attribute the "async" and "await" keywords when needed.

Playing with tasks

Tasks is something really enjoying. Once you understand exactly how they work there are some interesting properties you may take advantage of. First of all, once you are able to await a task, you can also decide to omit the await declaration so you get a method running completely asynchronous in a few. But here is a number of recipes in using Tasks:

Make your methods awaitable

Obviously it is important to create your own async methods. Imagine to create a business layer for your app that uses asynchronous methods from web service calls and you understand that the best is to declare async all the methods and then use await every time you call them. To achieve this result you have simply to return the result as you do normally but declare the method as Task<T> where T is the returning type.

   1: private async Task<Catalog> GetCatalog(string season)
   2: {
   3:     Catalog myCatalog = new Catalog();
   4:     await FillCatalog(myCatalog, season);
   5:     return myCatalog;
   6: }
   7:  
   8: // called as
   9: Catalog spring = await GetCatalog("Spring");

Await void methods

It may sounds silly but there are cases where you may need to await for the completion of async methods returning "void". In this case the methods must not return void but simply "Task" and it must not have a return statement. This enables to await the completion. It is the case of the FillCatalog method:

   1: private async Task FillCatalog(Catalog myCatalog, string season)
   2: {
   3:     WebService service = new WebService();
   4:     IEnumerable<Product> products = await service.GetProductsBySeasonAsync(season);
   5:     myCatalog.Products = products.ToList();
   6: }
   7:  
   8: // called as
   9: Catalog myCatalog = new Catalog();
  10: await FillCatalog(myCatalog, "Spring");

Await multiple methods chaining on after the other

Lot of times you have to call multiple async methods to accomplish a task. Think at having a number of web service calls to fill the resulting instance. When every call depends on from the previous you can create a chain of tasks and await for completion of all the tasks in the correct sequence:

   1: private async Task<Catalog> GetNextSeason()
   2: {
   3:     WebService service = new WebService();
   4:     IEnumerable<string> seasons = await service.GetAvailableSeasonsAsync();
   5:     string next = seasons.FirstOrDefault();
   6:  
   7:     if (next != null)
   8:         return await GetCatalog(next);
   9:  
  10:     return null;
  11: }

Await multiple methods running parallel

Sometime makes sense to call a series of asynchronous tasks together to maximize performances, but wait to the completion of all the requests before to continue the work. This is easily accomplished using the Task.WhenAll method that creates a task from a series of tasks in a array. The resulting task will complete when all the original task are completed:

   1: private async Task<Catalog []> GetYearlyCatalog()
   2: {
   3:     WebService service = new WebService();
   4:  
   5:     IEnumerable<string> seasons = await service.GetAvailableSeasonsAsync();
   6:  
   7:     return await Task.WhenAll(
   8:         from s in seasons
   9:         select GetCatalog(s));
  10: }

Catch an exception raised by a task

Handling exceptions in an asynchronous scenario is another thing really boring. With TPL this returns to be a common task achieved as usual using a try-catch block.

   1: try
   2: {
   3:     Catalog c = new Catalog();
   4:     await FillCatalog(c, "Spring");
   5: }
   6: catch (Exception ex)
   7: {
   8:     MessageDialog dialog = new MessageDialog(ex.ToString());
   9:     dialog.ShowAsync();
  10: }

Something has disappeared

Someone, especially if he has already developed asynchronous operations with Silverlight and WPF, can have noticed that there is a missing actor in this scenario. Using async methods previous than the Framework 4.5 need the repeated use of the Disparcher to marshal the context of the thread when you access the user interface. This because the rendering of the UI is made in a thread that is different from the one that is working the asynchronous request. In metro the Dispatcher is still available and I figure out there may be some cases when you may need to use it. But the new async-await model change the game drastically. Since the threading is left behind the scenes, most of the times there is not any need to use the Dispatcher as previous. Given that you explicitly start a task and pretend to update the user interface from inside of it here is the way to do it. Please take note that in the very first lines of the method I take a reference to the CodeDispatcher from Window.Current just because this property is valid only when it is called in the main thread:

   1: private async void test_Click(object sender, RoutedEventArgs e)
   2: {
   3:     CoreDispatcher dispatcher = Window.Current.Dispatcher;
   4:  
   5:     await Task.Run(() =>
   6:         {
   7:             dispatcher.RunAsync(
   8:                 CoreDispatcherPriority.Normal,
   9:                 () => this.test.Content = this.RollDice(1));
  10:         });
  11: }

In the next number of this series I will start to speak about new controls in metro-style applications. It will be the moment to connect together argument and meet GridView, ListView and other new UI elements.


Subscribe

Comments

  • BartLannoeye

    Re: Windows 8 Metro: Asynchrony made easy


    posted by BartLannoeye on Jun 04, 2012 11:58
    I just spent the weekend researching and trying out async for Windows 8, should have waited a few more days :). But it's good to see my findings confirmed, great article.
  • AndreaBoschin

    Re: Windows 8 Metro: Asynchrony made easy


    posted by AndreaBoschin on Jun 04, 2012 13:20

    @BartLannoeye thanks for your comment. Expect other contents in this series :)

    Andrea

  • rcouch00

    Re: Windows 8 Metro: Asynchrony made easy


    posted by rcouch00 on Jun 26, 2012 18:22
    Clearly this article never made it to an editors desk after it passed through the language translator.

Add Comment

Login to comment:
  *      *       

From this series