Listbox, Why Art Thou Blanking?

One of the biggest performance changes for developers using Silverlight in Mango (WP 7.5) is off-thread input for ListBox. In a nutshell this basically means that all any flick or pan a user makes on a ListBox is handled by a dedicated thread, the Direct Manipulation (DM) thread, instead of the UI thread. By connecting the DM thread to the Compositor thread (that’s the one that does all the drawing to the screen independent of the UI thread) we have a ListBox that moves smoothly even when the UI thread is completely blocked.

Unfortunately this also comes with a price – the ListBox is now so responsive and moves so fast that the Listbox can run out of content to display to the user as the user is flicking, and blank – basically drawing nothing except for an updated ScrollBar (so the user knows he’s still moving in the ListBox, but it doesn’t help much) and the background while the UI thread scrambles to bring in new items to fill up the holes.

My ListBox isn’t blanking, why do I care about this blog post?

You care because you care about your users. You’re the kind of dev who knows that even though he’s got a great, powerful phone, not everyone does. You care because you are proud to call yourself a developer. You care because… ok, back to reality.

With the release of Tango, Windows Phone is now supported on lower powered machines which often have slower hardware and less memory, so even though your device shows no blanking, others may see blanking, especially if they’re rocking a new, lower powered, Tango phone. This post will help give you the tools that let your app run smooth, with hardly any blanks, even on those phones.

My ListBox is not blanking, but it’s really jerky during scroll

Holy Smokes Batman! Jerky scrolling is all but eliminated in the new ListBox, even for WP7 apps – are you sure you’re using it? There’s a good chance you’re using the original LongListSelector (LLS) from the toolkit, which doesn’t auto-update when you move your project to Mango. To take full advantage of the new ListBox improvements you need to download the latest toolkit from http://silverlight.codeplex.com and update to the new LLS which is based on the platform’s ListBox.

Why does blanking occur: the nitty gritty

There are a couple of common code / design reasons that cause a ListBox to blank, but in general it all boils down to the amount of time it takes to bring in a new item. The ListBox maintains a one screen buffer of items in each direction which moves with the user’s viewport as they scroll around. If the ListBox can’t fill that buffer in the direction of the scroll fast enough, you get blanking.

clip_image002

Diagram 1: In a stationary world, when there is no scrolling going on, the user will be looking at the center screen and there will be buffered items waiting to be shown in both directions

Filling the buffer takes a few steps, namely creating an Item Container (if a suitable one from the excess buffer in the other direction doesn’t already exist) and Data Binding the new item’s content (which kicks off the Measure pass). All of these updates occur on the UI thread and they all happen at once (not item by item) so if a flick is fast and the ListBox realizes that it needs to draw a full screen worth’s of items it will block the UI thread while it does just that.

clip_image004

Diagram 2: As the user moves downwards we balance the buffers by transferring the excess buffer from the top buffer (red) to the bottom buffer and re-databind it to the new data, maintaining 1 screen of buffer in each direction.

But if the user flicks again while the UI thread is blocked bringing in buffer items, we’ll get even more out of sync and move completely out of our buffer space – since there is nothing in the Control to draw (remember, the ListBox is still scrambling on the UI thread to bring the new items in, it’s just too slow) you just get the background, i.e blankness.

If there’s nothing to draw, why is the ListBox still moving? Or, look at it from another angle:

As we mentioned earlier, scrolling is now off-thread, so from the Compositor’s point of view it’s moving the ListBox and everything in it, the problem is that the UI thread hasn’t stuck anything in it (at that position) yet, so we blank.

clip_image006

Diagram 3: Fast flicking a few times get you into this situation, where we have excess buffer above which we are trying to move downwards, but because there is so much of it and the UI thread is blocked trying to bring these items in we don’t finish in time and the user sees blankness (black) until the items are ready, at which time they simply appear.

So why is *my* ListBox blanking? And how do I fix it?

Let’s run through a couple of common reasons why ListBoxes blank, with some proposed solutions to each one.

1. Using ValueConverters in your template

ValueConverters are great – they allow you to transform your data on the fly as they are being DataBound into your items. Unfortunately they incur a UI thread cost – we need to transition from Silverlight into User Code, run your converter and then return. If your converter is heavy or you’re using lots of them in your template, then this will introduce blanking.

Question to ask yourself: does this code look like it can run in a trivial amount of time across all of the elements being brought in for a given frame? If not, you probably should explore a different way of doing the DataBinding. For example,you can have the object translate the values on population / property get time instead of using a converter – even though this may break your Data Model this can significantly reduce the DataBinding cost (and you could always wrap your object in a ViewModel).

2. Complex DataTemplates

When an item is moved from one buffer to another during a scroll, ListBox is usually smart enough to determine that this is the same kind of item and just update the data in the item. While this might sound cheap this dirties the item causing it to be remeasured. If your template is complex you will find that a lot of your time is wasted in Measure – remeasuring the layout of the control now that the data has been updated.

Fixing this is very per-scenario. Some general guidelines are to make sure to only use a container if you really need the options it provides – do you have a Grid within a Grid? Could you replace all of your Grids with a simple Canvas or maybe a StackPanel?

3. Decoding images on the UI thread

By default all images are decoded synchronously on the UI thread, so if you have something like this:

<Image Source=”{Binding ImageUrl}”/>

you’re going to block the UI thread for however long it takes to decode your image. Luckily there’s an easy fix for this, change your template to read as follows:

<Image>
  <Image.Source>
    <BitmapImage UriSource=”{Binding ImgUrl}” CreateOptions=”BackgroundCreation”/>
  </Image.Source>
</Image>

Note that this does come with some caveats – older images will still show up until the new ones are loaded and the user may see a visual pop-in of the image when it is done loading, but these can all be worked around and massaged into a nice user experience that is not harmed by excessive image decode.

For further details see this blog post.

4. Using PrepareItemForContainerOverride to dynamically select a template

A simple list item is often times just not good enough – your app has an image item, a text item, a video item, a link item etc. etc. and you have a different template for each one of them. A common pattern is to use the ListBox’s PrepareItemForContainerOverride callback to dynamically change the container’s template based on what item is being databound.

Unfortunately, doing this can completely throw off the ListBox’s buffering technique – the ListBox sees that the container that it had in its buffer is not the type that you need and junks it, wasting even more time on the march to blankness.

So how do I solve this? Surprisingly enough, it is often cheaper to have all of your template parts in one large template (yes, yes, I know about point 2 above – keep reading!) with each mini-template collapsed if it does not apply. Since collapsed template items incur next to no cost during layout, they have next to no impact on run time (though there is a slightly larger memory cost).

And how do I get my different items to display on the different templates? Simple – databind to a new property on your classes which either has a type enumeration that runs through a value converter (ItemTypeToVisibility), these kinds of converters are often cheap, or wrap your class in a UI view model so that it has a property that returns the Visibility type directly.

5. Pulling data from [favorite heavy source] as part of your binding

The properties you bind to should have simple getters (setters is a different story) – always. If you have logic like this:

public int Rating
{
  get
  {
    <read from database>   
    – or –
    <read from IsoStore>
    – or –
    <parse out some XML>
    – etc –   
 
}
}

then you’re doing something wrong. This kind of logic is fine for a property that you know is only read very rarely, if at all, but if it’s in a ListBox then it most likely is going to be seen and you should be initializing the data up front.

Don’t get me wrong – you don’t need to load everything as you are pulling in 1000 items to your list, but you can certainly do it on a background thread as a deferred task kicked off in the constructor of your object. If your objects are really heavy and memory is becoming an issue then you have a few possible routes:

  • Implement a completely delayed load by hooking into the ListBox’s scroll amount or compression states and only loading more items when you get to the end of the list
  • Implement a DoubleLinkedObservableCollection, where each item in the collection knows about the next node (in each direction) and when it gets databound (based on one of the properties) it notifies X number of nodes on each side to make sure they all have their data ready to go. This should be done on a background thread, just don’t forget to Dispatch back to update any properties that raise a PropertyChanged event.

6. Cut down background work

With only one core any background thread can interfere with the smoothness of the UI thread. Although background threads get a much smaller time slice compared to the UI thread, enough of them vying for time will effectively starve the UI thread.
clip_image008

Diagram 4: Fictional time slice showing the effects of more background threads (not to scale)

Across any given slice of time, the UI thread will be allowed to run longer than any other thread, but is effectively running at the same priority, hence it is forced to yield to the background threads when its time is up. The more background threads, the longer it is before you get back to the UI thread so that it can complete its task.

Moral of the story? Be wise about the number of active background, especially in high stress scenarios. Stick to using a thread pool so that you can queue up your work without it all trying to run at the same time.

I’ve tried all the above, it’s still blanking and I only have a couple of screens of data – help!

If you find yourself blanking on a relative small set of data (approximately 3-5 screens), then it might be time for drastic measures. As mentioned above the cause for blanking is the amount of time it takes to bring in new items with new data as the user is scrolling, i.e. the virtualization overhead is the culprit. If you disable virtualization on your list, you’ll generate of all your items up front and scrolling will be blankless. To do this, add the following to your XAML:

<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel>
</StackPanel>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>

While making this change is relatively simple, the impact can be drastic – no more blanking, but it comes at a cost:

  1. Startup time for the Page increases – we now have to realize more items, so it’s going to take longer
  2. Memory – same as above, more items hanging around, larger memory cost

If you give it a try on a low end device and startup and memory look acceptable – then this is your magic bullet!

So what are we left with?

Hopefully at this point you have a non to minimally blanking list, which scrolls smoothly and generally delights your users! Have you run into any other pitfalls that you think others should be warned of? Let us know below!

Running into other, unrelated performance issues? Drop us a line and we’ll see if we can focus on them in a future blog post (stay tuned for a “memory” series coming soon).