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.
public IEnumerable<T> ObservableCollection
Example
<DataGrid ItemsSource="{Binding Path=ReorderableData.ObservableCollection}" />
Synchonrizing the underlying collection.
public void Sync()
public void SyncReset()
Inserting items into the correct location to maintain sort order.
public void InsertInOrder(T item)
Example
ReorderableData.InsertInOrder(new ExampleClass());
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.
public void Sort()
Example
ReorderableData.Sort();
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.
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.