Keeping apps fast and fluid with asynchrony in the Windows Runtime
Wednesday, March 21, 2012 5:23 AM
Human beings are asynchronous by nature, which directly affects how we expect apps to respond to us. The Windows Runtime (WinRT) has embraced this asynchrony as a first-class citizen in the building of fast and fluid Metro style apps. If you are building a Metro style app, you will need to write some asynchronous code at some point. In this blog post, we talk about why asynchronous programming is so prevalent in WinRT, and we’ll give you the basics on how to use it in your apps and some background on how it works.
Fast and fluid apps must be responsive
How many times have you been using a Windows app that stops responding, and you are presented with a greyed version of the app and the spinning donut? Without fail, it always seems to come at the worst possible time. Worse yet, you might end up losing a lot of hard work when this happens.
Users expect apps to be responsive to all interactions. When they use their favorite news reading app, they want to add news feeds, read news articles, save news articles, etc. They should be able to do all of these things even when the app is retrieving the latest articles from the Internet.
This becomes especially important when a user is interacting with your app using touch. Users notice when the app doesn’t “stick to your finger.” Even minor performance problems can degrade the user experience and break the fast and fluid feeling.
An app isn’t fast and fluid if it stops responding to user input. So why do apps stop responding? One major cause is that the app is synchronous; it is waiting for something else to finish (like getting data from the Internet) and can’t respond to your input.
Many modern apps connect to social websites, store data in the cloud, work with files on the hard disk, communicate with other gadgets and devices, etc. Some of these sources have unpredictable latencies, which makes creating fast and fluid apps challenging. Unless built correctly, apps spend more time waiting for the outside environment and less time responding to the user’s needs.
Addressing this connected world was a core principle when we started to design APIs for the Windows Runtime (WinRT). We felt it was important to provide an API surface that was powerful and led to fast and fluid apps by default.
To achieve those goals, we made many potentially I/O-bound APIs asynchronous in the Windows Runtime. These are the most likely candidates to visibly degrade performance if written synchronously (e.g. could likely take longer than 50 milliseconds to execute). This asynchronous approach to APIs sets you up to write code that is fast and fluid by default and promotes the importance of app responsiveness in Metro style app development.
For those of you unfamiliar with asynchronous patterns, think of asynchrony in WinRT as providing a callback number to somebody over the phone. You give the person the number to call you back, you hang up, and then you continue doing any other work you need to. When the person is ready to talk to you, they can call you back at the number you provided. This is the essence of how asynchrony works in WinRT.
To understand more about the asynchronous nature of WinRT, we will first look at how you can use these asynchronous APIs in a straightforward way. Then we will take an under the hood look at what async primitives WinRT has introduced (that the new language features build upon), and how they work.
How to use it: new developer enhancements for asynchrony
In the past, writing asynchronous code was difficult. Our teams building tools for Windows 8 Metro style apps made significant advancements in solving this problem in recent releases. In the .NET Framework 4.0, we introduced the Task Parallel Library. Then the await keyword was introduced into C# and Visual Basic. C++ invested in technologies like the Parallel Patterns Library (PPL). And JavaScript has great tools like Promises.
Each one of these technologies presents you with a straightforward way to write asynchronous code. By introducing a standard async pattern under the hood that all of these technologies use, we allow you to use these technologies when building Metro style apps regardless of which programming language you use.
In the case of your favorite news app, the code that remains responsive while retrieving news articles is quite simple using theWindows.Web.Syndication.SyndicationClient API. The call to RetrieveFeedAsync returns immediately. This is a great thing because the results might take a long time to come back from the Internet. In the meantime, the UI continues to interact with the user.
The code highlighted in yellow runs after the call to RetrieveFeedAsync is completed. You don’t need to think about all the “start again where you left off” plumbing. And the code is written in a straightforward way as if it was synchronous.
JavaScript code to retrieve RSS feed asynchronously:
var title; var feedUri = new Windows.Foundation.Uri("http://www.devhawk.com/rss.xml"); var client = new Windows.Web.Syndication.SyndicationClient(); client.retrieveFeedAsync(feedUri).done(function(feed) { title = feed.title.text; });
C# code to retrieve RSS feed asynchronously:
var feedUri = new Uri("http://www.devhawk.com/rss.xml"); var client = new Windows.Web.Syndication.SyndicationClient(); var feed = await client.RetrieveFeedAsync(feedUri); var title = feed.Title.Text;
VB code to retrieve RSS feed asynchronously:
Dim feedUri = New Uri("http://www.devhawk.com/rss.xml"); Dim client = New Windows.Web.Syndication.SyndicationClient(); Dim feed = Await client.RetrieveFeedAsync(feedUri); Dim title = feed.Title.Text;
C++ code to retrieve RSS feed asynchronously:
auto feedUri = ref new Windows::Foundation::Uri("http://www.devhawk.com/rss.xml"); auto client = ref new Windows::Web::Syndication::SyndicationClient(); task<SyndicationFeed^> retrieveTask(client->RetrieveFeedAsync(feedUri)); retrieveTask.then([this] (SyndicationFeed^ feed) { this->title = feed->Title->Text; });
There is no mention of threads, context switching, dispatchers, etc. These new developer enhancements for writing asynchronous code will handle that for you. Code following the async API call is automatically called back in the same context that the original call was made. This means you can go ahead and update the UI with the results without worrying about getting back to the UI thread.
How to use it: error handling
Of course, API calls can fail (like losing network connectivity while retrieving RSS feeds). To be truly robust, we need to be resilient in the presence of errors. Using the async functionality in the languages makes handling errors more straightforward. The mechanism to do this varies based on the language.
For JavaScript, we recommend that you always end your Promise chains with done. This ensures that any exceptions captured in the promise chain are visible to the developer (i.e. reported in the error handler, or thrown). Done has the same signature as then. So to handle errors, you simply pass an error delegate into done():
var title; var feedUri = new Windows.Foundation.Uri("http://www.devhawk.com/rss.xml"); var client = new Windows.Web.Syndication.SyndicationClient(); client.retrieveFeedAsync(feedUri).done(function(feed) { title = feed.title.text; }, function(error) { console.log('an error occurred: '); console.dir(error); });
To handle the exception in C# or Visual Basic, you use a try/catch block just as you do with synchronous code today:
var title; var feedUri = new Uri("http://www.devhawk.com/rss.xml"); var client = new Windows.Web.Syndication.SyndicationClient(); try { var feed = await client.RetrieveFeedAsync(feedUri); title = feed.Title.Text; } catch (Exception ex) { // An exception occurred from the async operation }
The most common method to use in C++ to handle async errors is to use another task-based continuation that throws the exceptions (you can find other methods for error handling in C++ in the topic Asynchronous Programming in C++ on Dev Center):
auto feedUri = ref new Windows::Foundation::Uri("http://www.devhawk.com/rss.xml"); auto client = ref new Windows::Web::Syndication::SyndicationClient(); task<SyndicationFeed^> retrieveTask(client->RetrieveFeedAsync(feedUri)); retrieveTask.then([this] (SyndicationFeed^ feed) { this->title = feed->Title->Text; }).then([] (task<void> t) { try { t.get(); // .get() didn't throw, so we succeeded } catch (Exception^ ex) { // An error occurred } });
For more details on how to use these asynchronous improvements (including more scenarios like support cancellation and progress) see:
- Asynchronous Programming in the Windows Runtime on the Windows Dev Center
- Asynchronous Programming in JavaScript on the Windows Dev Center
- Asynchronous Programming in C++ on the Windows Dev Center
- Asynchronous Programming in .NET on the Windows Dev Center
- How to Handle Errors when using Promises in JavaScript on the Windows Dev Center
How it works: WinRT async primitives
Async is very powerful, but it can appear that there is a lot of magic that happens here on your behalf. To demystify what is happening, let’s take a look at how asynchrony in WinRT works in a little more detail. Our first step is to investigate the primitives the model is built upon. Most of you won’t have to use these primitives at all (if you find a scenario where you have to do this, we would love to hear the feedback).
Starting with our C# code, let’s look at the actual return type of RetrieveFeedAsync (shown here in C# Intellisense):
RetrieveFeedAsync returns an IAsyncOperationWithProgress interface. IAsyncOperationWithProgress is one of 5 interfaces that define the core asynchronous programming surface for WinRT: IAsyncInfo, IAsyncAction, IAsyncActionWithProgress, IAsyncOperation, and IAsyncOperationWithProgress.
The core interface that the WinRT async model is built on is IAsyncInfo. This core interface defines properties of an async operation like current status, the ability to cancel the operation, the error of a failed operation, etc.
As we mentioned earlier, async operations can return results. Async operations in WinRT can also report progress as they are running. The other 4 async interfaces above (IAsyncAction, IAsyncActionWithProgress, IAsyncOperation, andIAsyncOperationWithProgress) define different combinations of results and progress.
Providing core async primitives in the Windows Runtime enables C#, Visual Basic, C++, and JavaScript to project WinRT async operations in a familiar way to you.
Note: The move from cold start to hot start
In the Windows 8 Developer Preview released at //build, all async operations in WinRT were started cold. They didn’t start running immediately. They had to be explicitly started by the developer or implicitly started using the async features in C#, Visual Basic, JavaScript, or C++. We received feedback from several sources that this wasn’t intuitive and could cause pain and confusion.
In the Windows 8 Consumer Preview release, you will notice that we removed the Start() method from the WinRT async primitives. Now, our async operations are all started hot. So, the async method launched the operation before returning it to the caller.
This is just one of many changes that you’ll find in the Consumer Preview where we’ve fine-tuned the developer platform based on all the great feedback you have given us.
How it works: results, cancellation, and errors with async primitives
An async operation starts in the Started state and can progress to one of three other states: Canceled, Completed, and Error. The current state of an async operation is reflected in the Status property of the async operation (represented by the AsyncStatus enum type).
The first thing we do with an async operation is wire up a completed handler. From within the completed handler, we can get the results and use them.
IAsyncOperationWithProgress<SyndicationFeed, RetrievalProgress> op; op = client.RetrieveFeedAsync(feedUri); op.Completed = (info, status) => { SyndicationFeed feed = info.GetResults(); UpdateAppWithFeed(feed); };
Sometimes you may want to cancel an operation. You can do this by calling the Cancel method on the async operation.
IAsyncOperationWithProgress<SyndicationFeed, RetrievalProgress> op; op = client.RetrieveFeedAsync(feedUri); op.Cancel();
The Completed handler is always called for an async operation regardless of whether it was completed, canceled, or resulted in an error. Call GetResults only when the async operation was completed (e.g. not when the operation is canceled or ends in an error). You can determine this by inspecting the status parameter.
IAsyncOperationWithProgress<SyndicationFeed, RetrievalProgress> op; op = client.RetrieveFeedAsync(feedUri); op.Cancel(); op.Completed = (info, status) => { if (status == AsyncStatus.Completed) { SyndicationFeed feed = info.GetResults(); UpdateAppWithFeed(feed); } else if (status == AsyncStatus.Canceled) { // Operation canceled } };
This code still isn’t robust if the operation failed. Just like cancellation, error reporting is supported through the same async operation. As well as using AsyncStatus to differentiate between Completed and Canceled, we also use it to determine failure of an async operation (i.e. AsyncStatus.Error). You can find the specific failure code in the ErrorCode property of the async operation.
op.Completed = (info, status) => { if (status == AsyncStatus.Completed) { SyndicationFeed feed = info.GetResults(); UpdateAppWithFeed(feed); } else if (status == AsyncStatus.Canceled) { // Operation canceled } else if (status == AsyncStatus.Error) { // Error occurred, Report error } };
How it works: reporting progress with async primitives
Some WinRT async operations provide progress notifications while they are running. You can use these notifications to report the current progress of the async operation to the user.
In WinRT, progress reporting is handled through the IAsyncActionWithProgress<TProgress> andIAsyncOperationWithProgress<TResult, TProgress> interfaces. Each interface contains a Progress event that you can use to get progress reports from the async operation.
The programming model to consume this is nearly identical to consuming the Completed event. In our example of retrieving syndication feeds, we can report how many bytes of the total amount have been retrieved:
op = client.RetrieveFeedAsync(feedUri); float percentComplete = 0; op.Progress = (info, progress) => { percentComplete = progress.BytesRetrieved / (float)progress.TotalBytesToRetrieve; };
In this case, the second parameter (progress) is of type RetrievalProgress. This type contains two parameters (bytesRetrieved andtotalBytesToRetrieve) that are used to report our ongoing progress.
For deeper material (involving asynchronous programming in WinRT, or asynchrony in general), you can check out:
- “Async Everywhere” talk from the //Build conference
- Asynchronous Programming in JavaScript with “Promises”
- Task-Based Asynchronous Pattern
- Asynchrony in .NET
In closing
What’s interesting about asynchrony being so prevalent in WinRT is how it impacts the way you structure your code and architect your app. You start thinking about your app in a more loosely-connected way. If you have any feedback in this area, we would love to hear from you! Let us know by leaving a comment here or on the Dev Center forums.
We want your customers to love your Windows 8 apps. We want those apps to start alive with activity, and remain alive with activity. And we want you to more easily create those apps. We want you to be able to easily connect to social websites, store data in the cloud, work with files on the hard disk, communicate with other gadgets and devices, etc., while providing your customers with a great user experience.
--Jason Olson
Program Manager, Windows
No comments:
Post a Comment