WPF 4.5 – Part 7 : Accessing collections on non-UI Threads

, , 15 Comments

Here is the seventh (OMG !) post on the WPF 4.5 new features. Collections are a part of every application and their management is maybe the first thing you learn to deal with in WPF.

To begin, you put everything on the main(the UI one) thread and it works just fine. After a few time, you realize that it freezes the application UI and that the users usually don’t like it. Then you put the collections creation/feeding on another Thread to free the UI’s one and you realize that it is not possible because : “This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread”.

In this post we will discover that this is over with WPF 4.5 (and that you’ll love it) !

How do we do in WPF 4.0 ?

Let’s say that you have an application which display peoples on a list. The loading of the full list is quite long because it requires to go to a database via a WCF services and then to come back with the information.

In WPF 4.0 you have two different solutions:

  1. Retrieve the data on the UI Thread and wait after every add for the UI Thread to update. I don’t personnaly like this solution because this a kind of a hack and the interface reactivness remains odd.
  2. Tell the user to wait, then retrieve the data on a secondary Thread. Pass the information to the UI Thread and creates the list on it. This is usually what I do even if it’s degrade the code readability because it’s more complex to write.

Here is a an example of this second solution:

[csharp]
private void LoadUpPersonsClick(object sender, RoutedEventArgs e)
{
Task.Factory
//Retrieve the persons on another thread.
.StartNew>(RetrieveTheCollection)
.ContinueWith(t =>
{
foreach (var p in t.Result) _persons.Add(p);
},
//Continue on the UI Thread
TaskScheduler.FromCurrentSynchronizationContext());
}

public List RetrieveTheCollection()
{
List persons = new List();
for (int i = 0; i < 10; i++)
{
persons.Add(new Person() { Name = "Person " + i, Age = 40 + i % 5 });
}
return persons;
}
[/csharp]

How do we do in WPF 4.5 ?

Life is really easier in WPF 4.5: all you have to do is to to enable the features via the EnableCollectionSynchronization method. For the record, I struggled a lot to find this method which is a static one of the BindingOperations class.

It takes two parameters: the collection on which access across multiple threads will be enabled and a object which will be used as a lock. Easy as this snippet:
[csharp]
//Creates the lock object somewhere
private static object _lock = new object();

//Enable the cross acces to this collection elsewhere
BindingOperations.EnableCollectionSynchronization(_persons, _lock);[/csharp]

There is one another overload which takes a CollectionSynchronizationCallback callback and a could-be-null context as additionna parameters. With this one you could decide which synchronisation mechanism to use instead of the default one which is the lock.

So as you can imagine, all the synchronisation work is done for you by the framework and the code you will finally end-up to write will be something like that:
[csharp] private void AccessTheCollectionFromANonUIThreadClick(object sender, RoutedEventArgs e)
{
Task.Factory.StartNew(EditTheCollection);
}

public void EditTheCollection()
{
for (int i = 0; i < 10; i++)
{
_persons.Add(new Person() { Name = "Person " + i, Age = 20 + i % 5 });
}
}
[/csharp]

It is clearly more readable, easy to understand and to write !

As usual, a full project can be found on my Dropbox folder after registration.

Thanks to the WPF team for this great new feature !

Regards,

 

15 Responses

  1. JorgeBNYC

    26/09/2011 15 h 33 min

    Cool, I could have used this for the past 10 years! This feature, along with the "Delay" feature could help my life a lot! Now, if they could only make a "self contained" install of the .net run time I could start playing with this right now. By self contained I mean to have the .net run time in the same folder as my program rather than installed system wide.

  2. Brad Buhrkuhl

    26/09/2011 21 h 45 min

    I have always solved this with a little trickery in a custom extended ObservableCollection. Basically manually do the event subscription and use 2 backing private events with one for things registered on the dispatcher and one that is not. Then when the collection changed stuff has to fire, call the dispatcher stuff on the dispatcher thread and everything else off it. Code sample here: http://pastebin.com/EWApZ7j7 I also use this same technique for the INotifyPropertyChanged event.

Comments are closed.