Condividi tramite


Respond to User Input

The fourth of the four principles I mentioned in Optimizing Apps for Lower Cost Devices is Respond to User Input.

A responsive UI is a basic expectation that users have of apps.  Failing to respond to user input can frustrate users and can ultimately drive them away to other apps that are more responsive.

The basic guidance here is to keep as much activity off of the UI thread as possible until absolutely necessary.  The UI thread is what processes user input for you, so any code you execute on that thread will interfere with this processing.  Unless you're updating elements of your UI, you generally should not be executing code on the UI thread.

A quick way to check whether a block of code is running on the UI thread is to call Deployment.Current.Dispatcher.CheckAccess() inside that block of code (Intellisense won't show this method but it is there and useful).  This will return true if the code is executing on the UI thread, so if this returns true and you're not updating your UI, then you've found code that could be moved to background threads.

An easy way to move code to background threads is to wrap it in a call to ThreadPool.QueueUserWorkItem().

 // Running on the UI thread.
...
 // Running on a background thread.
ThreadPool.QueueUserWorkItem((o) =>
{
    ...
}

The ThreadPool is (as its name suggests) a pool of background threads that are waiting to execute work items for you.  QueueUserWorkItem enables you to register blocks of code that will execute on the next available background thread in the ThreadPool.  Note that use of ThreadPools is common and encouraged across many platforms (including Windows 8) so familiarizing yourself with them will serve you well in future projects.

When you are ready to update your UI, you will need to return to the UI thread.  You can return to the UI thread at any time from a background thread by wrapping your code in a call to Deployment.Current.Dispatcher.BeginInvoke().

 // Running on a background thread.
ThreadPool.QueueUserWorkItem((o) =>
{
    ...

    // Running on the UI thread.
    Deployment.Current.Dispatcher.BeginInvoke(() =>
    {
        ...
    }
}

If you try to update your UI from a background thread, you will receive an UnauthorizedAccessException with the message "Invalid cross-thread access."  In these cases, wrap the offending code in a call to Deployment.Current.Dispatcher.BeginInvoke() as shown above to execute it on the UI thread as required.

In addition to being able to control whether your code executes on the UI thread or on background threads, you also have options to do the same with framework code in several places.

Image decoding is an expensive operation that can harm interactivity significantly if executed on the UI thread, particularly if you're loading many images at a time.  The framework allows you to specify BitmapCreateOptions to control whether image decoding executes on the UI thread or on background threads.  Be sure to use the BackgroundCreation option to move image decoding to background threads.

The built-in ProgressBar control has known performance problems that can harm UI performance as a result of its animations executing on the UI thread.  Use the SystemTray ProgressIndicator for the best performance as the ProgressIndicator is a shell component that will not interfere with your UI thread.  A bonus to using the ProgressIndicator is that your UX will more closely match the UX of the built-in experiences.

If you're using animations in your app, use Storyboards wherever possible as these run on an important background thread known as the compositor thread and will not interfere with your UI thread.  If you're familiar with Expression Blend it can be particularly useful for generating Storyboards.

With code now running off of the UI thread for a given page, you should also focus on optimizing navigation between pages.

To keep page load times and in-app navigation responsive, defer loading activities until the first frame is rendered. The guidance for optimizing startup time of your MainPage applies to optimizing the startup time of subsequent page loads as well. Enabling the TiltEffect is a great way to acknowledge user input and indicate that navigation is in progress. This will make your app look and feel more like the first party experiences as well.

Many apps add page transition animations to add style to in-app navigations. While this is a great practice to make your apps more dynamic, excessive use of transition animations can delay load times, particularly when the generation/execution of the animations result in spikes in memory/CPU usage.  If transition animations are causing performance problems in your app, disabling them completely, or at least on low-memory devices only, can significantly improve in-app navigation performance.

Maintaining a responsive UI is important regardless of the platform you are targeting.  By offloading work to background threads and keeping in-app navigation fast, you'll provide the best possible experience to your users.