Click here to Skip to main content
15,867,453 members
Articles / Desktop Programming / WPF

Overcoming the demands placed on the UI Thread by constantly reordered data in an ObservableCollection

Rate me:
Please Sign up or sign in to vote.
4.72/5 (7 votes)
20 Oct 2014CPOL4 min read 16.6K   190   12   2
Provides an alternative to an ObservableCollection that is less UI intensive

Introduction

If you have worked with WPF, then you've no doubt encountered a problem, where in order to update an ObservableCollection you have to spend long periods of execution on the UI thread. Generally for a small number of updates this is not an issue, but when dealing with frequently updated data that constantly varies in order (an example would be best price from list of brokers) this can be problematic. This article looks at an approach to overcoming it.

Background 

I have encountered this problem a few times in my working career and came up with the idea for this solution when faced with a particularly high volume incident.

Typically in most environments I have been in, data is acquired remotely via some form of service and in all instances it is common sense to process the data via a background worker thread (task pools, ect.). However at some point the data needs to be displayed and when it involves more than just property updates, we will typically need to call a BeginInvoke or an Invoke on the dispatcher thread. If you have done this, you will typically know how constantly invoking the dispatcher, can quickly render a UI unresponsive.

So when do we need to call BeginInvoke or an Invoke? Only when the collection needs to be changed, i.e. an item is add, removed, or its order changed. So how can we prevent that happening? The answer is we can't for items that need to be add or removed, but in the case of reordered data there is an alternative.

In a typical ObservableCollection, you may decide to handling reordering by either a CollectionViewSource or by swapping the items that need to change position as the value determining the sort index changes. Both approaches need to be performed on the UI thread and if called frequently it will degrade the UI's responsiveness. The approach that I have taken, is to rather than look at the properties of a class in the traditional OO approach (as belonging solely to one class), is to instead look at them as interchangeable. What that means in praticial terms, is instead of objects changing location, only their data is swapped. The obvious advantage to this is that property changes can be made on worker threads and so therefore have no impact on UI performance.

This is achieved this by wrapping an ObservableCollection and only updating it by adding or removing items, when the collection grows or shrinks. At all other times, the contents of the items are swapped, allowing the processing to be performed on background threads and thus freeing the UI thread to update the UI.

Although this approach works well for collections of a certain size, but as collections get larger, performance will quickly degrade as sorting an inserting will generally requires large number of iterations.

Using the Code

The zip file is divided into three projects;

ReorderableObservableCollection The ReorderableObservableCollection source

ReorderableObservableCollection.Tests A small unit test project

ReorderedCollectionExample An example of the performance differences and how to use the collection.

ReorderableObservableCollection

For the most part the collection can be treated like any other collection, but it is worth remembering there is one important thing to remember. You cannot rely on ReferencesEquals to determine if the objects contained in the collection are the same, as the contents will be swapped and so therefore you will need to implement IEquatable<>.

The differences

In order to achieve the performance, the observable collection is wrapped by the class and exposed as an IEnumerable collection. It is designed to work by allowing changes to be made in batches and then for the changes to be synchronized, which will then be performed on the UI thread.

To bind the collection to a control, you need to reference the ObservableCollection property.

C++
//
// This property exposes the wrapped observable collection and should be bound
// to the control used to display the collection in your view
//
public IEnumerable<T> ObservableCollection
 
Example
<DataGrid ItemsSource="{Binding Path=ReorderableData.ObservableCollection}" />

Synchonrizing the underlying collection.

C++
//
// Updates the observable collection with any changes made
//
public void Sync()


//
// Updates the observable collection from the maintained list and resets the changes.
// If a large number of changes have been made then this can be more efficient.
//
public void SyncReset()

Inserting items into the correct location to maintain sort order.

C++
//
// Inserts an item into the correct location in the collection, based on its IComparable<>
// implementation
//
public void InsertInOrder(T item)
 
Example
ReorderableData.InsertInOrder(new ExampleClass());


//
// Inserts an item into the correct location in the collection, based on its 
// IComparer<T> implementation
//
public void InsertInOrder(IComparer<T> comparer, T item)
 
Example
public class SortOrder : IComparer<ExampleClass>
{
  public int Compare(ExampleClass x, ExampleClass y)
  {
     return x.Index.CompareTo(y.Index);
  }
}

ReorderableData.InsertInOrder(this, new ExampleClass());

Sorting the collection into order.

C++
//
// Performs a quick sort on the collection using its IComparable<> implementation
//
public void Sort()
 
Example
ReorderableData.Sort();


//
// Performs a quick sort on the collection, using a supplied on its IComparer<T> implementation
//
public void Sort(IComparer<T> comparer)
 
Example
public class SortOrder : IComparer<ExampleClass>
{
  public int Compare(ExampleClass x, ExampleClass y)
  {
     return x.Index.CompareTo(y.Index);
  }
}

ReorderableData.Sort( new SortOrder());

The demo program

The demo program is designed to show the differences in load placed upon the UI thread when sorting ObservableCollection, against the ReorderableObservableCollection which does not touch it all when sorting.

Example

The top data grid is bound to the ReorderableObservableCollection and the lower grid to the ObservableCollection. You will notice a sleep value, which is the sleep given to the thread updating the ObservableCollection. Reduce this sleep and you will quick find the UI becomes unresponsive, increase and you will find the number of updates decreases. This could be optimised so that calling of BeginInvoke is throttled, but in a commercial application there will be other UI activities occurring, which are not simulated by the demo.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Technical Lead
United Kingdom United Kingdom
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
BugCompilation error due to Rx Pin
FantasticFiasco27-Oct-14 0:01
FantasticFiasco27-Oct-14 0:01 
QuestionGood idea Pin
AnotherKen21-Oct-14 10:24
professionalAnotherKen21-Oct-14 10:24 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.