Поделиться через


Async CTP – Task based asynchronous programming for Windows Phone

async

await

6 code samples for this post

Intro

Asynchronous programming is super-interesting especially today, with responsiveness required by all modern devices. In the context of Windows Phone 7, Async CTP Version 3 was released last year, and it’s compatible with SDK 7.1, Silverlight 5 and Roslyn CTP. Our takeaways today is using async in the context of Windows Phone 7, understanding TAP (task based asynchronous programming). I’ll show you how deep the rabbit hole goes: we’ll go beyond basics to exceptions, cancellations, progress, switching threads, combinators and even re-entrancy.

Historical Background

And let me just say: it’s worth learning NOW, because this model is quickly becoming mainstream, especially with Windows 8 not that far on the horizon. In terms of the where we are with Async CTP Version 3: you don’t need to wait for C# 5.0 to start using it. It has been a long time since 1960s when Steve Russel was one of the first to add continuations to LISP on IBM 704. I like this definition of continuations:

Say you're in the kitchen in front of the refrigerator, thinking about a sandwich. You take a continuation right there and stick it in your pocket. Then you get some turkey and bread out of the refrigerator and make yourself a sandwich, which is now sitting on the counter. You invoke the continuation in your pocket, and you find yourself standing in front of the refrigerator again, thinking about a sandwich. But fortunately, there's a sandwich on the counter, and all the materials used to make it are gone. So you eat it. :-)

Scheme formally introduced call/cc operations.

The good and the ugly of asynchronous programming

Most of us used asynchronous programming: from creating threads to synchronization, it’s not an easy process. Creating one thread may be easy, but once we get to composition, it becomes really ugly. Trying to make one thread execute after another, cancel upon a condition, becomes almost impossible on the large scale. We start creating those horrendous state machines that are very hard to understand and write. Fortunately, it’s been proven that the asynchronous code can me transformed into a state machine by means of merely a mechanical transformation.  So, we can keep the clean beautiful code that looks like a natural C# code, while the compiler will translate it into a state machine for us behind the scenes. And that’s the promise of Async CTP.

Getting Started

To get started, we’ll need Async CTP, install it then add a reference for Windows Phone specific library. 

image

Example 1: Old-fashion asynchronous code

This post contains 6 code samples, the first one is an example of an old-fashion asynchronous code which uses some lambdas. On the button click we invoke DoWork method, which uses WebClient’s asynchronous DownloadStringAsync call. We use a lambda to handle DownloadStringCompleted event. Note that since SDK 7.1, WebClient only supports asynchronous calls. However, this isn’t using Task<T> API we’re looking for. Some …Async names in the old code may be a bit deceiving. Note, when I click Run button the rest of the UI becomes irresponsive, because the UI thread is blocked by Thread.Sleep method call.image 

 private void button1_Click(object sender, RoutedEventArgs e)
        {
            DoWork();
            //MessageBox.Show("Done");
        }

        void DoWork()
        {
            light.Fill = new SolidColorBrush(Color.FromArgb(255, 255, 0, 0));
            var client = new WebClient();
 // in the new Async CTP code we won’t need this!
             client.DownloadStringCompleted += (s, e) =>
            {
                Process(e.Result);
            };
            client.DownloadStringAsync(new Uri("https://www.weather.gov"));
        }

        List<string> Process(string data)
        {
            Thread.Sleep(5000);
            const string _LINK_REGEX = "href=\"(?<url>(((ht|f)tp(s?))\\://)?((([a-zA-Z0-9_\\-]{2,}\\.)+[a-zA-Z]{2,})|((?:(?:25[0-5]|2[0-4]\\d|[01]\\d\\d|\\d?\\d)(?(\\.?\\d)\\.)){4}))(:[a-zA-Z0-9]+)?(/[a-zA-Z0-9\\-\\._\\?\\,\\'/\\\\\\+&%\\$#\\=~]*)?)\"";
            List<string> result = new List<string>();
            MatchCollection matches = System.Text.RegularExpressions.Regex.Matches(data, _LINK_REGEX);
            foreach (var m in matches)
            {
                result.Add(m.ToString());
            }
            return result;
        }

        void Done()
        {
            light.Fill = new SolidColorBrush(Color.FromArgb(255, 0, 255, 0));
        }

Example 2: New style code

Let’s move on to the new style asynchronous code. Notice the difference. We add async modifier to button1_Click event, because it now awaits for the results of the asynchronous DoWorkAsync code. Notice that Thread.Sleep call in the asynchronous code is replaced with TaskEx.Delay.

 

  private async void button1_Click(object sender, RoutedEventArgs e)
        {
            List<string> data = await DoWorkAsync();
            Done();
        }

        async Task<List<string>> DoWorkAsync()
        {
            light.Fill = new SolidColorBrush(Color.FromArgb(255, 255, 0, 0));
            string data = await new WebClient().DownloadStringTaskAsync("https://www.weather.gov");
            return await ProcessAsync(data);
        }

        async Task<List<string>> ProcessAsync(string data)
        {
            const string _LINK_REGEX = "href=\"(?<url>(((ht|f)tp(s?))\\://)?((([a-zA-Z0-9_\\-]{2,}\\.)+[a-zA-Z]{2,})|((?:(?:25[0-5]|2[0-4]\\d|[01]\\d\\d|\\d?\\d)(?(\\.?\\d)\\.)){4}))(:[a-zA-Z0-9]+)?(/[a-zA-Z0-9\\-\\._\\?\\,\\'/\\\\\\+&%\\$#\\=~]*)?)\"";
            var res = await TaskEx.Run(() =>
             {
                 List<string> result = new List<string>();
                 MatchCollection matches = System.Text.RegularExpressions.Regex.Matches(data, _LINK_REGEX);
                 foreach (var m in matches)
                 {
                     result.Add(m.ToString());
                 }
                 return result;
             });
            await TaskEx.Delay(5000);
            return res;
        }

        void Done()
        {
            light.Fill = new SolidColorBrush(Color.FromArgb(255, 0, 255, 0));
        }

Example 3: Exception Handling a la “async”

Asynchronous exception handling doesn’t look any different from the standard exception handling, and that’s the beauty of it.

             try
            {
                List<string> data = await DoWorkAsync();
                Done();
            }
            catch (Exception)
            {
                MessageBox.Show("Exception");
            }

By simply adding try {} catch {} blocks we can handle exceptions bubbling up all the way from nested async calls. Think what you’d need to write if you were doing it the old fashion way? This may look really ugly.

Example 4: Cancellations

With cancellations we can cancel execution of our tasks (in fact an entire composition tree, or individual tasks in the composition) by using a CancellationToken. Notice that we can catch the OperationCancellationException, which gives our code a clean natural look and feel.

 CancellationTokenSource _cancellation;
  _cancellation = new CancellationTokenSource();
            try{
                List<string> data = await DoWorkAsync(_cancellation.Token);
                MessageBox.Show("Done");
            }
            catch (OperationCanceledException)
            {
                MessageBox.Show("Operation Cancelled");
            }
  

Example 5: Combinators

Combinators, such as WhenAll or WhenAny are great to combine multiple asynchronous tasks into one call. In this example 3 asynchronous tasks are combined into one WhenAll call, which awaits for all calls to complete.

 

  Uri[] uris = { new Uri("https://www.weather.gov"), new Uri("https://www.weather.gov/climate/"), new Uri("https://www.weather.gov/rss/") };
 string[] pages = await TaskEx.WhenAll(from uri in uris select new WebClient().DownloadStringTaskAsync(uri, cancellationToken));
  

Example 6: Asynchronous-style MVVM

In the final example I used a standard Pivot application template for Windows Phone, which generates an MVVM model. I slightly modified the model itself to make it async. With very little code it shows what MVVM is capable of. By changing the pre-defined LoadData method (adding async modifier), I can leverage Netflix OData feed with Async CTP:

 

 public async void LoadData()
        {
            try
            {
                var items = await DownloadDataAsync();

                foreach (var item in items)
                    Items.Add(item);

                this.IsDataLoaded = true;
            }
            catch (Exception)
            {

            }
        }

        public async Task<IEnumerable<ItemViewModel>> DownloadDataAsync()
        {
            string netflix = "https://odata.netflix.com/Catalog/Titles?$filter=ReleaseYear eq {0}&$skip={1}&$top={2}&$select=Url,ReleaseYear,Rating,Runtime,AverageRating,BoxArt";
            string query = String.Format(netflix, 2010, 0, 20);
            var data = await new WebClient().DownloadStringTaskAsync(query);
            var items = await ParseDataAsync(data);
            return items;
        }

        public async Task<IEnumerable<ItemViewModel>> ParseDataAsync(string data)
        {
            XNamespace xa = "https://www.w3.org/2005/Atom";
            XNamespace xd = "https://schemas.microsoft.com/ado/2007/08/dataservices";
            XNamespace xm = "https://schemas.microsoft.com/ado/2007/08/dataservices/metadata";
            var items = await TaskEx.Run( () => from entry in XDocument.Parse(data).Descendants(xa + "entry")
                        let properties = entry.Element(xm + "properties")
                        select new ItemViewModel
                        {
                            Title = (string)entry.Element(xa + "title"),
                            Url = (string)properties.Element(xd + "Url"),
                            ReleaseYear = (string)properties.Element(xd + "ReleaseYear"),
                            Rating = (string)properties.Element(xd + "Rating"),
                            Length = string.Format("{0} min", (int)Math.Round(int.Parse("0" + (string)properties.Element(xd + "Runtime")) / 60.0)),
                            UserReview = new string('*', (int)Math.Round(decimal.Parse("0" + (string)properties.Element(xd + "AverageRating")))),
                            BoxArtUrl = (string)properties.Element(xd + "BoxArt").Element(xd + "LargeUrl")
                        }
            );
            return items;
        }

Conclusion

Feel free to download the included examples, as well as great examples that come with Async CTP, especially Async 101.

6 code samples for this post