Designing Windows Store Apps for Performance

  • 5/5/2014

Live content

Many apps need to display the most recent information to the users at startup or during page navigation. News apps obviously fall into this category, but so do shopping apps, financial apps, movie and music apps, and many others. Typically, these apps pull content from services in the cloud and, as discussed earlier, that presents a challenge, because networks are inherently unreliable. I go through numerous techniques you can use to improve the user experience when handling live content. The topics are

  • Prioritize your content, and make sure it is available.
  • Use caching to reduce downloads.
  • Use ContentPrefetcher to load data in advance.
  • Asynchronous I/O.
  • Extended splash screen.

Prioritize your content, and make sure it is available

First you should prioritize your content. All your content is not going to be equally important to the users. Your design should reflect this. You need to figure out which are the most important resources and make sure the app handles these before anything else. Basically, you want to identify all the resources needed to populate the first screen of the app or at least part of it.

Your app should retrieve these resources as soon as possible and defer everything else. The goal is to get the data needed and nothing more, present that to the user, and make sure the app is responsive as quickly as possible. All other resources should be handled once the app is responsive.

Once your content is prioritized, you must make sure it is available. This can be tricky if your content isn’t local to the app. Any content you have to retrieve at run time might delay progress indefinitely. The only way to guarantee a fast experience is to rely on local resources.

Your app can attempt to download resources at startup, but it shouldn’t wait for these, or at least it should limit the wait to a few hundred milliseconds. In case online resources are not available, your app should have a fallback scenario that allows it to launch with slightly stale data. The prefetch feature discussed later in this chapter is a great way to refresh local caches even when the app isn’t running. This means that stale data might not be very old.

There are various ways to update local content with retrieved content once this is available. One approach is to use the FlipView in a way similar to what the Store app does. (I’m sure you’re familiar with the Store app, but for your convenience Figure 3-1 shows what I am talking about.) Your app can load a FlipView with local content and start retrieving additional content. Once this content is available, it can be added to the FlipView carousel. This provides a smooth transition between the two and guarantees a good user experience even when updated resources cannot be retrieved.

FIGURE 3-1

FIGURE 3-1 The Windows Store app uses the FlipView to display both cached and new content.

Use caching to reduce downloads

Caching is a standard feature of all HTTP-based protocols. If resources are cached, they are retrieved locally instead of through the network. Using caching can significantly reduce the need for retrieving data over the network, so the second thing you should do is make sure all your data is cached appropriately. Some resources might be valid only for seconds, but most resources will probably be relevant for minutes, hours, or even days. After all, even high-profile news sites don’t change their top story every minute.

Caching is controlled entirely by the server. As long as the server outputs the proper caching information, the Windows Internet (WinINet) stack caches resources as needed and you don’t have to do anything in the app itself to take advantage of this. The HttpClient class in Windows.Web.Http uses WinINet (but the HttpClient defined in System.Net.Http does not, so make sure you use the right one). If data is cached, it is retrieved locally instead of through the network. Once the caching expires, the resource will be retrieved and cached again on the next request. All of this is completely transparent to the app, so you don’t need to do anything in the app itself, but you need to make sure caching is handled correctly on the server.

In some cases, you might not control the caching on the server, such as when you’re integrating data from back ends you don’t control. In that case, you can introduce a façade server that retrieves the same data as your app would and adds caching information as necessary. Admittedly, this makes the back end part of the app more complex, but doing this improves the performance of your app for all users. If you’re doing this, you might also be able to rearrange the data to better suit your needs. Perhaps the original feed is more verbose than what your app needs. Trimming the data feed to your needs is a great way to reduce the amount of data downloaded and the time needed to handle it.

For non-HTTP-based protocols, you typically have to implement your own caching scheme.

Use ContentPrefetcher to load data in advance

Caching requires resources to be retrieved once, so you might be thinking how you can take advantage of caching on something like news stories and accompanying pictures. If the user has to retrieve the data before it can be cached, it is not going to benefit scenarios like that.

The ContentPrefetcher class introduced in Windows 8.1 aims at solving that specific problem. The idea is that you can specify—either directly or indirectly—resources that should be retrieved and cached even if your app is not running. Windows will then automatically retrieve those resources periodically and cache them on behalf of your app. This increases the chance that resources can be retrieved locally from the cache. Obviously, all the resources must support caching to take advantage of this. If they don’t, prefetching them will not make any difference.

It goes without saying that Windows cannot simply poll the resources every other second because that would drain the power of the device quickly and possibly exhaust the user’s data plan. Instead, Windows uses heuristics to determine what resources are downloaded and how. These heuristics take into account network and power conditions, app usage history, and the results of prior prefetch attempts to provide maximum user benefit. The bottom line is that Windows does this in an effective manner, but there is no guarantee that any particular resource will have been downloaded before a given app launches.

In a way, using ContentPrefetcher is a bit like owning a lottery ticket. You might not win anything, but the cost of the ticket is low. However, unlike a real lottery ticket, the odds of winning are pretty good in this case. Although the rewards will not yield you a new Ferrari, they will improve the performance of your app. If you ask me, that’s well worth the small cost.

Using ContentPrefetcher directly

Using ContentPrefetcher is straightforward. If the URIs (locations) of the resources are known to the app, the app simply configures a list of resources with the system and Windows attempts to retrieve them. This is the direct way of using ContentPrefetcher, and it works well if the URIs don’t change but the content returned does. Listing 3-1 shows how to use ContentPrefetcher when the list of resources is known to the app.

LISTING 3-1 Using ContentPrefetcher when the URIs are known to the app.

var resources = new[] {
    "http://windowsteamblog.com/windows/b/developers/atom.aspx",
    "http://windowsteamblog.com/windows/b/windowsexperience/atom.aspx",
    "http://windowsteamblog.com/windows/b/extremewindows/atom.aspx",
    "http://windowsteamblog.com/windows/b/business/atom.aspx",
    "http://windowsteamblog.com/windows/b/bloggingwindows/atom.aspx",
    "http://windowsteamblog.com/windows/b/windowssecurity/atom.aspx",
    "http://windowsteamblog.com/windows/b/springboard/atom.aspx",
    "http://windowsteamblog.com/windows/b/windowshomeserver/atom.aspx",
    "http://windowsteamblog.com/windows_live/b/windowslive/rss.aspx",
    "http://windowsteamblog.com/windows_live/b/developer/atom.aspx",
    "http://windowsteamblog.com/ie/b/ie/atom.aspx",
    "http://windowsteamblog.com/windows_phone/b/wpdev/atom.aspx",
    "http://windowsteamblog.com/windows_phone/b/wmdev/atom.aspx",
    "http://windowsteamblog.com/windows_phone/b/windowsphone/atom.aspx"
};

ContentPrefetcher.ContentUris.Clear();
foreach (var res in resources)
{
    ContentPrefetcher.ContentUris.Add(new Uri(res));
}

Listing 3-1 adds a number of URIs to the list of ContentUris on ContentPrefetcher. This populates a global list, so unless you clear the list first, you’ll simply add to the existing list. Furthermore, ContentPrefetcher limits the number of ContentUris to 40 per app. If you add more than 40 URIs, ContentPrefetcher throws Exception. (For some reason, it doesn’t throw a more specific exception.) The bottom line is you should either check the content of ContentUris or clear the content before adding to the list. Moreover, you must limit the number of resources to 40 or fewer per app.

The preceding code instructs Windows to retrieve the resources according to the heuristics discussed earlier. To test that prefetching works as expected, you can force the system to fetch the configured resources through the IContentPrefetcherTaskTrigger::TriggerContentPrefetcherTask method. Unfortunately, there’s no managed wrapper for this call yet, so you have to do this from a C++ app. See the “Triggering prefetching” sidebar for an example of a small C++ console app that triggers fetching for a specific app.

LISTING 3-2 C++ code for the TriggerPrefetch utility.

#include "stdafx.h"
#include <IContentPrefetcherTaskTrigger.h>
#include <roapi.h>
#include <winstring.h>


int _tmain(int argc, _TCHAR* argv[])
{
    WCHAR* activableClass = L"Windows.Networking.BackgroundTransfer.ContentPrefetcher";
    int iLen = wcslen(activableClass);
    HSTRING hs_activableClass;
    int rc = 0;

    if (argc > 1) {
        if (SUCCEEDED(WindowsCreateString(activableClass, iLen, &hs_activableClass))) {
            if (SUCCEEDED(RoInitialize(RO_INIT_MULTITHREADED)))
            {
                IContentPrefetcherTaskTrigger *trigger = nullptr;
                if (SUCCEEDED(Windows::Foundation::GetActivationFactory(hs_activableClass,
                                                                        &trigger))) {
                    // supply PackageFullName at prompt, retrieve the name by running
                    // Get-AppxPackage cmdlet
                    if (FAILED(trigger->TriggerContentPrefetcherTask(argv[1]))) {
                        // log error and set bad return value
                        rc = -1;
                    }
                    trigger->Release();
                }
            }
            RoUninitialize();
        }

        if (FAILED(WindowsDeleteString(hs_activableClass))) {
            // log error and set bad return value
            rc = -2;
        }
    }
    else {
        printf("Syntax: TriggerPrefetch <PackageFullName>\n");
    }

    return rc;
}

Using ContentPrefetcher indirectly

For some apps, the direct approach is not very attractive. For example, a news app might retrieve stories and corresponding images whose URIs change all the time. Today’s top story has a different URI than yesterday’s top story, so the app has no way to enumerate the resources it wants to prefetch. To handle this situation, ContentPrefetcher offers an indirect way to specify the resources to be fetched. When you use the indirect approach, the ContentPrefetcher queries a single resource for a list of resources to retrieve. That is, the list of resources is always available at the same URI, but the content returned through this list differs over time. This allows news apps and the like to constantly refresh the cache based on the new resources on the server side.

The list of resources is just an XML document. The document must conform to the schema in Listing 3-3. Listing 3-4 shows the same resources you used in the example in Listing 3-1 as a proper XML file for ContentPrefetcher. With the XML file in place, you just need to set the IndirectContentUri property as shown here:

ContentPrefetcher.IndirectContentUri = new Uri("http://localhost:46449/resources.xml");

This configures ContentPrefetcher to retrieve the list of resources—in this case, called resources.xml—from the specified URI. For the purpose of this example, I am just using a local server, but obviously you should point to a real URI to retrieve the list.

LISTING 3-3 Schema for IndirectContentUri.

<?xml version="1.0" encoding="utf-8"?>
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified"
 xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="PrefetchUris">
    <xs:complexType>
      <xs:sequence>
        <xs:element minOccurs="1" name="uri" type="xs:anyURI" />
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>

LISTING 3-4 Sample XML file for IndirectContentUri.

<?xml version="1.0" encoding="utf-8" ?>
<prefetchUris>
  <uri>"http://windowsteamblog.com/windows/b/developers/atom.aspx"</uri>
  <uri>"http://windowsteamblog.com/windows/b/windowsexperience/atom.aspx"</uri>
  <uri>"http://windowsteamblog.com/windows/b/extremewindows/atom.aspx"</uri>
  <uri>"http://windowsteamblog.com/windows/b/business/atom.aspx"</uri>
  <uri>"http://windowsteamblog.com/windows/b/bloggingwindows/atom.aspx"</uri>
  <uri>"http://windowsteamblog.com/windows/b/windowssecurity/atom.aspx"</uri>
  <uri>"http://windowsteamblog.com/windows/b/springboard/atom.aspx"</uri>
  <uri>"http://windowsteamblog.com/windows/b/windowshomeserver/atom.aspx"</uri>
  <uri>"http://windowsteamblog.com/windows_live/b/windowslive/rss.aspx"</uri>
  <uri>"http://windowsteamblog.com/windows_live/b/developer/atom.aspx"</uri>
  <uri>"http://windowsteamblog.com/ie/b/ie/atom.aspx"</uri>
  <uri>"http://windowsteamblog.com/windows_phone/b/wpdev/atom.aspx"</uri>
  <uri>"http://windowsteamblog.com/windows_phone/b/wmdev/atom.aspx"</uri>
  <uri>"http://windowsteamblog.com/windows_phone/b/windowsphone/atom.aspx"</uri>
</prefetchUris>

Tips for using ContentPrefetcher

ContentPrefetcher has a LastSuccessfulPrefetchTime property that returns a nullable DateTimeOffset indicating when the last prefetch (if any) executed. You can use this to display a message about when the content was retrieved.

Common to both the direct and indirect approach is that they work only with the HttpClient class defined in the Windows.Web.Http namespace. ContentPrefetcher does not work with the HttpClient class from the System.Net.Http namespace, so you have to make sure you use the right HttpClient for this to work.

I hope this goes without saying, but setting up ContentPrefetcher should not be part of the critical path for the launch experience. ContentPrefetcher doesn’t alter the current launch, but it might improve subsequent launches by making resources available locally. Consequently, this is something you should set up once the app is up and running.

Asynchronous I/O

Even with the help of ContentPrefetcher, many apps still need to retrieve resources from the cloud at run time. Retrieving data from the network easily takes hundreds of milliseconds, and it might even take seconds to complete. In the past, it was easy to block the UI thread doing I/O like that. Blocking the UI thread for more than 50 milliseconds (ms) or so can make your app appear unresponsive. Fortunately, WinRT and C# make it easy to write asynchronous code, which prevents you from blocking the UI thread because of I/O.

Despite the asynchronous design of the WinRT API and the excellent support for writing asynchronous code in C#, you still need to keep in mind a couple of pitfalls. Although asynchronous I/O prevents the UI thread from blocking and thus keeps your app responsive, it doesn’t change how long a given operation takes. If it takes 400 ms to download some resource, making this operation asynchronous allows your app to do other work while waiting for the operation to complete, but it doesn’t change the fact that the app has to wait 400 ms. Asynchronous code allows your app to do something while it is waiting, but it doesn’t change the duration of the wait.

Consider Listing 3-5. It retrieves an RSS feed asynchronously and does some work as represented by the DoMoreWork method once that operation has completed. Using await accomplishes two things here. It makes SomeMethod return immediately so that the caller is not blocked, and it captures the state and the remaining code of the method so that it can run once RetrieveFeedAsync completes. Now assume that retrieving this RSS feed takes 400 ms. That means the total running time for SomeMethod is 400 ms or more. The calling thread can do other work in the meantime, but DoMoreWork doesn’t run until at least 400 ms have passed.

LISTING 3-5 Simple asynchronous method.

public async void SomeMethod()
{
    var feedUri = new Uri("http://windowsteamblog.com/windows/b/developers/atom.aspx");
    var client = new Windows.Web.Syndication.SyndicationClient();
    var feed = await client.RetrieveFeedAsync(feedUri);

    DoMoreWork();
}

If DoMoreWork needs the result of the asynchronous operation, this approach makes perfect sense, but it doesn’t change the fact that the invocation of the method is delayed by the duration of the asynchronous operation in RetrieveFeedAsync.

This is something to keep in mind when you design the launch and navigation experiences for your app. For example, if you await an asynchronous operation before you create the main frame in the OnLaunched method, the launch experience is delayed by the duration of said operation because the app cannot proceed before the asynchronous operation completes.

Similarly, if you wait before an asynchronous data source is fully populated before you set up binding, the app will not show any data before all the data is available. This might be desirable for a reliable, fast data source, but if the data source could introduce arbitrary delays, this likely turns into a bad user experience. On the other hand, if you set up binding and then populate the data source asynchronously, the app displays data as it is added to the data source. If any of the elements take a long time to retrieve, they will not show up before they are ready, but at least the app remains responsive and shows the data that is available while the remaining data is retrieved.

Cancelling asynchronous operations

When you write asynchronous code, the await keyword acts like a rendezvous point in the code. Whenever the asynchronous method completes, your code continues to execute just as if the call had been synchronous.

That’s great, until you realize that “whenever” is unbounded. Your code could end up waiting forever to run. If an asynchronous calls stalls forever, the remaining code will never run.

If that’s a concern, you want to be able to detect situations like that and cancel the asynchronous operation. For instance, if your app requests live content at startup, you should implement a fallback mechanism that cancels the outstanding requests and provides content in an alternate fashion if the request takes too long.

Using CancellationToken with Task

If you are familiar with the Task Parallel Library (TPL), you probably know about CancellationToken. A CancellationToken is useful when you want to cancel a running task. You simply pass in the token and your task can then check whether cancellation has been requested. If you want to cancel a CPU-bound Task, you should use a CancellationToken, just like you would when using TPL for desktop or server applications. Listing 3-6 shows a simple example of doing this.

LISTING 3-6 Using CancellationToken to cancel a long-running computation.

using System;
using System.Threading;
using System.Threading.Tasks;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

// The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234238

namespace CancelTask
{
    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();
        }

        // input for Fibonacci calculation
        private int Counter = 35;

        private CancellationTokenSource Cts;

        private async void Calculate_Click(object sender, RoutedEventArgs e)
        {
            Cts = new CancellationTokenSource();
            try
            {
                Output.Text = (await LongRunningComputationAsync(Cts.Token)).ToString();
            }
            catch (OperationCanceledException)
            {
                Output.Text = "Cancelled";
            }
        }

        private void Cancel_Click(object sender, RoutedEventArgs e)
        {
            Cts.Cancel();
        }

        private Task<int> LongRunningComputationAsync(CancellationToken token)
        {
            return Task.Run(() => RecursiveFibonacci(Counter++, token), token);
        }

        // Very ineffective implementation of Fibonacci
        // For illustration purposes only - Don't use!
        private int RecursiveFibonacci(int n, CancellationToken token)
        {
            if (n <= 1)
            {
                return 1;
            }

            token.ThrowIfCancellationRequested();

            return RecursiveFibonacci(n - 2, token) + RecursiveFibonacci(n - 1, token);
        }
    }
}

Listing 3-6 sets up two event handlers. The first event handler triggers a long-running, asynchronous calculation. In this case, the calculation is an extremely inefficient implementation of Fibonacci. There are much better ways to implement this function. The only nice feature of this implementation is that it is simple to read. The performance of this implementation is horrible, so please don’t use this code.

The interesting thing about this implementation is that it takes a CancellationToken as input and checks whether cancellation has been requested. If cancellation is requested, it throws an OperationCanceledException, which must be caught by the caller. The exception is automatically marshalled from the worker thread running the asynchronous operation to the calling thread that initiated the operation.

The second event handler simply signals that cancellation was requested. This is done by calling the Cancel method on the CancellationTokenSource. This changes the state of the CancellationToken and aborts the asynchronous operation with an OperationCanceledException.

Notice how the CancellationToken in LongRunningComputationAsync is passed to both RecursiveFibonacci and to Task.Run. The reason for this is that both methods need to be able to detect whether cancellation was requested. If cancellation was requested before Task.Run executes, no task is scheduled. If cancellation was requested while the calculation was in progress, the call to ThrowIfCancellationRequested will cancel it.

Using CancellationToken with WinRT APIs

Task and CancellationToken are .NET Framework concepts and thus unknown to the WinRT APIs. That is, none of the asynchronous WinRT methods take a CancellationToken as input, so by default the approach in Listing 3-6 doesn’t work with WinRT methods. Luckily, the WindowsRuntimeSystemExtensions class provides the AsTask extension method that turns the object returned by a WinRT asynchronous method into a Task.

The WindowsRuntimeSystemExtensions class provides many overloads of the AsTask extension method, and several of those take a CancellationToken. In other words, the AsTask extension method effectively bridges the gap between Task-based asynchronous methods and WinRT-based asynchronous methods. All you need to do to use a CancellationToken with WinRT-based methods is call the AsTask extension method on the objected returned by the asynchronous method.

Listing 3-7 shows how you can use a CancellationToken to cancel an asynchronous WinRT method.

LISTING 3-7 Using CancellationToken with WinRT-based asynchronous methods.

using System;
using System.Threading;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

// The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234238

namespace CancelAsyncWinRT
{
    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();
        }

        private CancellationTokenSource Cts;

        private async void Download_Click(object sender, RoutedEventArgs e)
        {
            Cts = new CancellationTokenSource();

            var feedUri = new Uri("http://windowsteamblog.com/windows/b/developers/atom.aspx");
            var client = new Windows.Web.Syndication.SyndicationClient();

            // Disable cache, so method runs for a while
            client.BypassCacheOnRetrieve = true;
            var feed = client.RetrieveFeedAsync(feedUri).AsTask(Cts.Token);

            try
            {
                Output.Text = (await feed).Title.Text;
            }
            catch (OperationCanceledException)
            {
                Output.Text = "Cancelled";
            }

        }

        private void Cancel_Click(object sender, RoutedEventArgs e)
        {
            Cts.Cancel();
        }
    }
}

Listing 3-7 is similar to the code in Listing 3-6. However, this time the app calls RetrieveFeedAsync on SyndicationClient. That’s a native, asynchronous WinRT method. By default, this doesn’t support the use of a CancellationToken. Notice how AsTask turns the object returned by RetrieveFeedAsync into a Task<SyndicationFeed>. The AsTask overload takes a CancellationToken as input, which enables you to cancel the WinRT API call just like you would do if this was a regular .NET task. Furthermore, you can await the Task just like any other awaitable type, and once the asynchronous operation completes, you can fetch the Title from the downloaded feed.

Extended splash screen

The MSDN guidelines state that your app can use an extended splash screen if it needs more time during startup. The reasons for using an extended splash screen are twofold. First of all, you want to prevent Windows from terminating the app because of excessive startup time. Second, you might want to let users know that the app is still launching. The extended splash screen typically replaces the system-controlled splash screen with an identical or similar screen that is displayed while the app loads.

From the point of view of Windows, the app is actually running at this point. Using an extended splash screen prevents Windows from shutting down your app because it spends too much time getting started, but it doesn’t change the fact that the user has to wait a long time for the app to become responsive. Waiting several seconds for an app to launch is not a good user experience, extended splash screen or not.

Many apps simply display their logo accompanied by a progress indicator as their extended splash screen. There might be a few users out there who get all excited by seeing spinning dots for seconds, but for the rest of us that’s not a great experience. I’m not a big fan of extended splash screens, but if your app must use an extended splash screen at least make sure it adds some value. You can beef up the progress indicator by telling the user what the app is doing. I don’t feel that it adds a lot of value, but it does break the monotony, which makes it a tad better than the progress indicator on its own. Instead, try to come up with some interesting information you can show the user. This could be in the form of helpful tips for using the app, interesting stats about the usage, or something else that might make the user interested in exploring new areas of the app.

Extended splash screens make sense for advanced games or apps that have to process large amounts of data on startup. Most regular apps should not need an extended splash screen, and using one should definitely be the last resort. Make sure your app’s start experience is as fast as it can get using the tips covered in this chapter. Before you even consider using an extended splash screen, you should measure the startup performance of your app and optimize the critical path as described in Chapter 6, “Investigating performance issues.” It is likely that you can optimize the launch experience to the point where you don’t need an extended splash screen. All things considered, a faster startup makes for a much better user experience than simply using an extended splash screen to mask performance issues.