To show data to the user you have to load data and bind data – but which one is the slow part? Answer: It depends on the nature of your data and the complexity of your UI. This post will show you that data can be loaded on the UI thread or the background thread, and data can be bound all at once or a few items at a time. These data handling techniques can be used together to create one of the most important concepts in Windows Phone 7 – perceived performance. Full source with demo app showing the scenarios can be found at the bottom.
These methods, including binding items in batches, has been proposed by other before but here I try to collect the good parts.
Update 2011-05-27: The code has been given a slight modification, please see “the incremental”.
Intro
Often you need to load data and show to the user immediately when entering a new page. Getting that data from where it sits in storage to the eyes of the user should be done as fast as possible. Below I’ll show you a few ways of doing this. The scenario is that 1500 items are cached in IsolatedStorage and should be loaded. A ListBox on the page will be populated with 100 items. Data is loaded using binary serialization and takes around 0.3 s.
Starting the navigation | Target page with data bound and loaded |
The naïve implementation is to just load the data when the page is created (in NavigatedTo, or when using CaliburnMicro, OnActivated) and be happy about it. By loading on creation, you stop the phone from navigating to the new page immediately – data will be loaded and bound when you get to the page but it has to wait until the page has been created until navigating to it. This is not acceptable from a UX perspective.
protected override void OnActivate() { base.OnActivate(); LoadUsingBinary(); }
The slow
A slightly better way of loading the data is to offload the work to the Dispatcher. The data will still be loaded on the UI thread, but will have to stand in line with all other UI actions that need to take place – so the UI will have time to update first. Unfortunately the UI will still be blocked while data is being loaded.
Application.Current.RootVisual.Dispatcher.BeginInvoke(LoadUsingBinary);
The fast
Loading data, or any long running operation for that matter, should be done on a background thread. This will cause the UI to update and be responsive while data is being loaded, nice. One problem remains though, the list is still slow to appear and pops up long after data actually has been loaded.
var worker = new BackgroundWorker(); worker.DoWork += (sender, e) => LoadUsingBinary(); worker.RunWorkerAsync();
The incremental
The basic idea behind incremental population is to give the UI thread some slack: bind a few items then wait, let the UI bind and create the items, then feed it more. I love this method. I think it’s very rare the user needs the details of the 100 items right away. The usual case is that the user sees some items pop up, glances at them, then starts to scroll. By the time the user starts to scroll, the incremental population will have populated far more than what can fit on one screen – perceived performance FTW!
Update 2011-05-27: This code was originally written to populate with X nbr of items per timer tick, but was rewritten to only populate twice; first a few items, then the rest. The loading behaviour is the same but the list becomes response much faster. The code was never intended to be optimized, but I could not resist doing this minor change…
private void PopulateListUsingTimer() { if (_collectionOfDataItems == null) return; InitTimerVariables(); DispatcherTimer timer = CreateTimer(); timer.Tick += (sender, e) => HandleTick(sender); timer.Start(); } private void HandleTick(object sender) { IEnumerable items; if (_hasPopulatedFirstScreen) { ((DispatcherTimer)sender).Stop(); items = _collectionForIncrementalLoad. Skip(NBR_ITEMS_TO_INITIALLY_POPULATE_WITH); } else { _hasPopulatedFirstScreen = true; items = _collectionForIncrementalLoad. Take(NBR_ITEMS_TO_INITIALLY_POPULATE_WITH); } Items.AddRange(items); }
The code shown above only covers the population of items, the actual loading of items is in this case done on the UI thread, not in the background. This is where the trade offs begin. What strategy you should have for loading data and binding data heavily depends on the nature of your data.
Disclaimer: the code above is not intended to be optimal, just a way of showing you how the incremental loading can be accomplished. I think the e.g. list handling is awful, but I wanted to get this out to you, so thank you for you patience with reading bad code.
Comparison
So, what does this do for me IRL (in real life)? To test this we measure ttflbi – time to first list box item, i.e. the time from navigation is invoked on one page until the first list box items appears on the page being navigated to.
Measuring ttflbi
To measure time from invoking the navigation to actually seeing the first item on the screen I’m hooking up two event handlers. First I statically hook up to the ListBox’s LayoutUpdated, and in that handler, the first time I can find a control that I know only exists in an item (a CheckBox), I hook up to that item’s LayoutUpdated. When that event is first raised I stop the timer.
Comparison of ttflbi
Data load/bind method | ttflbi measurements (s) | average ttflbi (s) |
constructor | 2,70 – 2,17 – 2,10 | 2,32 |
dispatcher | 1,68 – 1,71 – 1,66 | 1,66 |
backgroundWorker | 2,00 – 2,94 – 2,69 | 2,54 |
incremental | 1,22 – 0,84 – 0,86 | 0,97 |
As you can see, the fastest incremental ttflbi is 0.84, just about twice as fast as the fastest of the others. (dispatcher at 1.66)
Conclusion
How you should go about loading and binding your data heavily depends on the nature of your data. Questions to ask:
- Is it a lot of data or will load time not be an issue?
- Can data be loaded incrementally (to be bound incrementally)?
- Is the UI layout complex?
I highly recommend you to introduce a few timers in your code and start printing stuff; you’ll learn a lot about where time is actually spent.
LayoutUpdated
The official performance guidance from Microsoft recommends that you wait for the LayoutUpdated event until initiating the heavy lifting for your page, good advice. I did not include that case in this test because you will roughly do the same as the dispatcher case that I already have. (Remember: I am using the LayoutUpdated events, but just to measure time, not to kick of the data loading)
VirtualizingStackPanel
The ListBox internally uses a VirtualizingStackPanel (VSP) to lay out its items. The virtualization is done by only loading a few screens of data at a time, so when scrolling fast you will after a few pages see black areas – this is the VSP not having loaded the items yet. As the result number show, despite the ListBox using a VSP, the incremental loading gives improvements in terms of “time to first item on screen”. Again, what the optimal case for you is can only be determined by you testing the methods out.
A note on “the fast”
Loading the data using a backgroundWorker is what I above refer to as “the fast”. The irony is that it is actually the slowest of the methods. The slowness is due to two reasons:
- To show when the GUI is busy and not busy, I have the default ProgressBar in there which animates on the UI thread and consumes cpu cycles. (you should always use the PerformaceProgressBar)
- Creating the backgroundWorker and switching to a background thread consumes cycles.
I hope this will help you create not only faster apps, but apps that feel faster. Thanks for listening, bye!