Async CTP is released again recently and announced in MIX 11. Just after it is released, the first thing that everyone looks for is what is new in the release. As a matter of fact, I did jumped back to see them but eventually found out that there is nothing new in this build in terms of new features is concerned but the release focuses on fixes of performance adding debugging capabilities etc. I will definitely look back to them later in another post, but in this post I am going to talk about another important thing that featured with this release. As opposed to the previous release, the current release now supports Silverlight and Windows Phone 7 environments. This seems to be interesting.
What is Asynchrony?
The word asynchrony means something that is running without blocking other operations running in parallel. If you have created a background Thread to process some data, you are actually doing asynchronous job in background as your foreground operation does not get hampered. In vNext C# introduces Asynchrony using TPL. The two new keywords “async” and “await” could be used to make one sequential method asynchronous. Hence the new way of developing asynchronous program replaces the traditional approach where we needed to refactor the code totally to gain asynchrony in our application. Basically, this is done using the StateMachine to store the entire method into a form of states, and each states are delegated into batch of statements. The Task.ContinueWith is used in the system to ensure that the method body gets executed sequentially. Yes, it’s a compiler trick. If you want to know more about it, please read through my entire article on “Async CTP”.
Now coming back to our discussion on Windows Phone 7, we know that C# async is now supported by both Silverlight and Windows Phone 7 out of box. To work with this, please follow the steps :
- Download "Async CTP (Refreshed)" from this link.
- After you install the CTP release, you will get few more dlls available in your installed directory as shown below :
Remember you need AsyncCtpLibrary_Phone.dll for Windows Phone 7 and AsyncCtpLibrary_Silverlight.dll for Silverlight.
- Create a new Windows Phone 7 application and add reference to the dll.
- You are done, now lets start creating an application.
Once you are done, lets use our Application Bar (If you want to know about it, read my post ) to create two methods.
<phone:PhoneApplicationPage.ApplicationBar> <shell:ApplicationBar IsVisible="True" IsMenuEnabled="True"> <shell:ApplicationBarIconButton IconUri="/Images/appbar.favs.addto.rest.png" Text="NEW" Click="ApplicationBarNewButton_Click" IsEnabled="True"/> <shell:ApplicationBarIconButton IconUri="/Images/appbar.favs.rest.png" Text="OLD" Click="ApplicationBarOldButton_Click" IsEnabled="True"/> </shell:ApplicationBar> </phone:PhoneApplicationPage.ApplicationBar>
We create two buttons each of which points to its respective eventhandler in the codebehind.
private async void ApplicationBarNewButton_Click(object sender, EventArgs e) { for (int i = 0; i < 10; i++) { this.txtStatus.Text = "Status moves to : " + i; await TaskEx.Delay(500); } } private void ApplicationBarOldButton_Click(object sender, EventArgs e) { for (int i = 0; i < 10; i++) { this.txtStatus.Text = "Status moves to : " + i; Thread.Sleep(500); } }
Here the two eventhandler looks a bit different. The ApplicationBarOldButton_Click loops through 10 times updating the status of the txtStatus and sleeping for 500 milliseconds. Hence once the update to the control is made the current UI Thread is sleeps for half a second.
On the other hand the ApplicationBarNewButton does the same thing, but uses TaskEx.Delay. Well, if you read my article on Async, you might already know, TaskEx.Delay is actually the same implementation of Thread.Sleep but uses Task based approach, hence it does not block the UI thread rather it calls back when the certain milliseconds is elapsed. In other words, the Old button will block the UI thread and hence makes it unresponsive while the New button will return the control back to the UI after the delay is registered.
Now if you run the sample you will notice some major performance benefits on the new approach. Even though everything is done in UI thread (as txtStatus would not going to update if we are in a new thread because of Thread Affinity on controls) for both the controls, yet the later goes on and update the status while the former hangs the UI totally.
In the figure, you can see, our status is displayed on the screen while the loop is getting executed, and the UI remains responsive throughout the execution of the code block.
The Second button on the other hand will hung up the UI and update the status only when the execution of the loop ends. Hence the intermediate status will not be updated when the Old button is clicked.
Now lets notice as your UI is responsive after the Async operation is getting executed, you can click on the New Button again and again. This is interesting. Yes, you can invoke multiple async blocks at a time and each of those calls can execute the code asynchronously on the same thread multiplexed.
So now you can see the status will be getting increased and again decreased because of two or more for loops are getting executing with TaskEx.Delay. Hence you can maintain the same logical flow of statement in the code while the compiler is doing all the heavy lifting (which it is good at) creating the lamdas for you so that the method can pause when await statement is encountered and later on continue execution of the same sequential block of code when the result is available.
Try out the sample App - 251KB
Netflix Demo on Windows Phone 7
The demo that I have just showed you is really a crap. Nothing in it as such. if you want to take a look at something better than this, you will have a complete sample application within your installation folder called Netflix. If you try out the sample, you can see the sample actually works great, getting all the movies asynchronously without losing the UI responsiveness. Let me describe the code a bit :
async void LoadMoviesAsync(int year) { var movieCollection = new ObservableCollection<Movie>(); var yearMovies = new YearMovies { Year = year, Movies = movieCollection }; yearPivot.Items.Add(yearMovies); yearPivot.SelectedItem = yearMovies; yearMovies.StatusText = ""; var pageSize = 10; var imageCount = 0; while (true) { yearMovies.StatusText = string.Format("Searching... {0} titles so far...", imageCount); var movies = await QueryMoviesAsync(year, imageCount, pageSize); if (movies.Length == 0) break; foreach (var movie in movies) movieCollection.Add(movie); imageCount += movies.Length; } yearMovies.StatusText = string.Format("{0} titles found", imageCount); } async Task<Movie[]> QueryMoviesAsync(int year, int first, int count) { var client = new WebClient(); var url = String.Format(query, year, first, count); string data = await client.DownloadStringTaskAsync(new Uri(url)); return await TaskEx.Run(delegate { var movies = from entry in XDocument.Parse(data).Descendants(xa + "entry") let properties = entry.Element(xm + "properties") select new Movie { Title = (string)entry.Element(xa + "title"), Url = (string)properties.Element(xd + "Url"), Year = (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 movies.ToArray(); }); }(Taken from Netflix Sample of Async CTP)
Basically, the two methods that you need to address are LoadMoviesAsync and QueryMoviesAsync. Literally the code looks very simple. The LoadMoviesAsync takes an year as input loops through until it gets at least a result from QueryMoviesAsync and puts it into an ObservableCollection which is bound to a ListBox in UI.
The QueryMoviesAsync on the other hand executes the WebClient to Download URI data as string using DownloadStringTaskAsync, and creates an array of the Movies from the whole string. TaskEx.Run is actually invoking in a new Thread, but you can await that (hence your code doesnt break).
Simple enough? Yes so you can minimize the complexity of the code yet without losing your market place approval.
Please Note : You can also implement the same using any pattern like MVVM in your code in the same way.
Working with IsolatedStorage in Async
Another interesting demo that is present with Async CTP samples is Async Background Threads. In this demo, the asynchronous operation is performed to deal with IsolatedStorage in the mobile. If you think of I/O operation, generally reading from memory device is really slow to handle. In case of IsolatedStorage as I have shown in my last post, you have to be very careful if your data in IsolatedStorage is huge. You need to load the application before 10 seconds otherwise your app will get rejected from Marketplace, and hence you need delay loading of data. The demo provided as sample application gets you through dealing with this without any complexity.
After you open the demo, you will see the first screen above. The Data will be loaded from the Web using Async and Await. It uses the same technique as Netfix demo to retrieve the data from RSS feed, but the main thing is it stores data when the application is closed to a file myDataFile.txt. Later on the next load, it will get the file from IsolatedStorage rather than the Web. Lets look at the code :
/// <summary> /// GetData is called on a background thread. If data is present in Isolated Storage, and its save /// date is recent, load the data from Isolated Storage. Otherwise, start an asynchronous http /// request to obtain fresh data. /// </summary> public async Task<Result> GetData() { // Check the time elapsed since data was last saved to Isolated Storage TimeSpan TimeSinceLastSave = TimeSpan.FromSeconds(0); if (IsolatedStorageSettings.ApplicationSettings.Contains("DataLastSave")) { DateTime dataLastSave = (DateTime)IsolatedStorageSettings.ApplicationSettings["DataLastSave"]; TimeSinceLastSave = DateTime.Now - dataLastSave; } // Check to see if data exists in Isolated Storage and see if the data is fresh. // This example uses 30 seconds as the valid time window to make it easy to test. // Real apps will use a larger window. IsolatedStorageFile isoStore = IsolatedStorageFile.GetUserStoreForApplication(); if (isoStore.FileExists("myDataFile.txt") && TimeSinceLastSave.TotalSeconds < 30) { // This method loads the data from Isolated Storage, if it is available. StreamReader sr = new StreamReader(isoStore.OpenFile("myDataFile.txt", FileMode.Open)); // Read the data from the isolated storage file asynchronously using the Async feature string data = await sr.ReadToEndAsync(); await PhoneWorkaround.TemporaryAsyncPhoneWorkaround; sr.Close(); return new Result() { Data = data, Source = "Isolated storage" }; } else { try { // Otherwise it gets the data from the Web. WebClient client = new WebClient(); //Download the string from the web asynchronously using the Async feature string data = await client.DownloadStringTaskAsync("http://windowsteamblog.com/windows_phone/b/windowsphone/rss.aspx"); await PhoneWorkaround.TemporaryAsyncPhoneWorkaround; return new Result() { Data = data, Source = "Web" }; } catch { return new Result() { Data = null, Source = "An unexpected error occured" }; } } } /// <summary> /// If data was obtained asynchronously from the Web or from Isolated Storage, this method /// is invoked on the UI thread to update the page to show the data. /// </summary> /// <param name="data"></param> /// <param name="source"></param> public async void SetData(string data, string source) { pageDataObject = data; var itemsCollection = new ObservableCollection<RSSItem>(); var items = await TaskEx.Run(() => from item in XElement.Parse(data).Element("channel").Elements("item") select new RSSItem {Title = (string)item.Element("title"), Url = (string)item.Element("link"), Date = (string)item.Element("pubDate")} ); foreach (var item in items) itemsCollection.Add(item); itemsListBox.ItemsSource = itemsCollection; // Set the Application class member variable to the data so that the // Application class can store it when the application is deactivated or closed. (Application.Current as AsyncPhoneBackgroundThreads.App).AppDataObject = pageDataObject; statusTextBlock.Text = "data retrieved from " + source + "."; }
The code is well commented. The first method actually gets data either form IsolatedStorageFile if exists or from WebClient. The DownloadStringAsync gets data from the RSS feeds and being an async method, it can await the call. Similar to this, each StreamWriter or StreamReader has Async methods available to invoke basic I/O operations. Here ReadToEndAsync is basically the async implementation of ReadToEnd method thus giving the control back immediately after invoking the call.
The SetData on the other hand will set the data in the ListBox. We create an ObservableCollection and pass the items into it. TaskEx.Run actually runs in different Thread to create the collection, which ensures that it will not block the UI using await.
Conclusion
If you think of what was there before and whats coming next with Async, you will easily understand that the introduction of async eventually eased out our work of dealing with complexity in asynchrony. You would definitely have the existing technology in place, but using the new pattern will let you concentrate only on the business logic and all the heavy lifting will be done by the compiler itself. I think this post will give you some rough idea about Async CTP in Windows Phone 7. Give your feedback.
Thank you for reading.
No comments:
Post a Comment
Please make sure that the question you ask is somehow related to the post you choose. Otherwise you post your general question in Forum section.