Welcome to Pete Brown's 10rem.net

First time here? If you are a developer or are interested in Microsoft tools and technology, please consider subscribing to the latest posts.

You may also be interested in my blog archives, the articles section, or some of my lab projects such as the C64 emulator written in Silverlight.

(hide this)

WPF 4.5: Observable Collection Cross-Thread Change Notification

Pete Brown - 16 January 2012

WPF 4.5 is available as part of the Visual Studio 11 Developer Preview released at the Build 2011 conference, and is part of the .NET Framework version 4.5. WPF 4.5 addresses several important customer requests including the ability to have cross-thread change notification for collections - the topic of this post.

Update 1/20/2012: I have posted a set of updates to this post with additional information about the details, as well as a few code changes. Be sure to read that post as well as this one.

The Problem

As I explained in my post on cross-thread change notification, Silverlight and WPF have the concept of the UI thread and background (or secondary) threads. Code on a background thread is not allowed to interact with UI elements (controls, etc.) on the foreground thread. You can find more information about the general problem in that post.

What happens when you need to add and remove items from a collection from that background thread? That's pretty common in applications which must keep a list updated (stock trading is an obvious example). When the collection raises the change notification events, the binding system and listeners, on the UI thread, have code executed. Remember, an event is just a delegate, or really, just a function pointer. So, essentially, the collection is calling a function on the UI thread. That's not allowed, and it throws an exception.

The approach pre-4.5 was to dispatch all calls to add items to the collection. The diagram from the Silverlight post applies here.

image

If code on the background thread needs to access elements on the UI thread, set a property for example, it needs to dispatch the call to the UI thread using the dispatcher or synchronization context. Get enough of this happening, such as when constantly updating a collection, and you can run into some real performance problems.

The 4.5 Solution

We actually have a good solution for this in WPF 4.5. The BindingOperations class includes the EnableCollectionSynchronization function. This function helps the binding system and the collection intelligently handle change notification.

Let's look at an example. The application, when run, will look like this:

image

The stocks in the ListBox are loaded by clicking the "Load" button. To test the change notification, click the Keep Loading button. For this example, the Keep Updating button doesn't do anything (more on that when I cover in-place sorting and updating in another post)

XAML

Just for grins, and to show its inclusion in 4.5, I've put the functionality into another new addition to WPF proper: the Ribbon. I didn't use commands although they are supported.

<RibbonWindow x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication2"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Ribbon>
<RibbonTab Header="First Tab">
<RibbonGroup Header="Group 1">
<RibbonButton x:Name="Foo" Label="Foo!" />
<RibbonButton x:Name="Bar"
Label="Bar!" />
</RibbonGroup>
<RibbonGroup Header="Group 2">
<RibbonButton x:Name="Load"
Click="Load_Click"
Label="Load" />
<RibbonButton x:Name="KeepLoading"
Click="KeepLoading_Click"
Label="Keep loading" />
<RibbonButton x:Name="KeepUpdating"
Label="Keep Updating" />

</RibbonGroup>
<RibbonGroup Header="Count">
<TextBlock Text="{Binding Stocks.Count}" />
</RibbonGroup>
</RibbonTab>
<RibbonTab Header="Second Tab">

</RibbonTab>
<RibbonTab Header="Stuff that doesn't go anywhere else">

</RibbonTab>
</Ribbon>

<Grid Margin="0,156,0,0">
<TextBlock VerticalAlignment="Top"
FontSize="20"
Text="WPF Rules" />

<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>

<ListBox ItemsSource="{Binding Stocks}"
VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.CacheLengthUnit="Page"
VirtualizingPanel.CacheLength="2,2"
VirtualizingPanel.ScrollUnit="Item">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>

<TextBlock Text="{Binding Symbol}"
Grid.Column="0" />
<TextBlock Text="{Binding Value}"
Grid.Column="1" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

</Grid>
</Grid>
</Grid>
</RibbonWindow>

For this example, the primary pieces to pay attention to are the ListBox which is bound to the stocks, and the ribbon buttons to Load and Update the stocks collection. Those use the Stock model item via the ViewModel.

The Model

The model consists of a single class, called Stock.

using System.ComponentModel;

namespace WpfApplication2.Model
{
class Stock : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;

private string _symbol;

public string Symbol
{
get { return _symbol; }
set { _symbol = value; NotifyPropertyChanged("Symbol"); }
}
private decimal _value;

public decimal Value
{
get { return _value; }
set { _value = value; NotifyPropertyChanged("Value"); }
}

protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}

The ViewModel

The ViewModel is where the action is. First the listing, and then the explanation.

using System.Threading;
using System.Threading.Tasks;
using System.Windows.Data;
using WpfApplication2.Model;

namespace WpfApplication2.ViewModel
{
class MainViewModel
{
public ObservableCollection<Stock> Stocks { get; set; }

private object _stocksLock = new object();

public MainViewModel()
{
Stocks = new ObservableCollection<Stock>();

BindingOperations.EnableCollectionSynchronization(Stocks, _stocksLock);
}


private Random _random = new Random();
public void AddNewItems()
{
for (int i = 0; i < 100; i++)
{
var item = new Stock();
for (int j = 0; j < _random.Next(2, 4); j++)
{
item.Symbol += char.ConvertFromUtf32(_random.Next(
char.ConvertToUtf32("A", 0),
char.ConvertToUtf32("Z", 0)));
}

item.Value = (decimal)(_random.Next(100, 6000) / 100.0);
Stocks.Add(item);
Debug.WriteLine(item.Symbol);
}
}


public void StartAddingItems()
{
Task.Factory.StartNew(() =>
{
while (true)
{
AddNewItems();
Thread.Sleep(500);
}
});

}
}
}

When the MainViewModel is instantiated, the collection is created. Next, I call the EnableCollectionSynchronization method, providing it the collection and an object to use as a lock. The lock object is nothing special, just an instance of the CLR object type. This single method makes cross-thread collection change notification possible.

The StartAddingItems method spins up another thread, using the Task parallel library, which then loads items into the collection. The data is randomly generated, but includes a large number of updates. When you run the application, you'll see that the ListBox is able to be quickly updated and that there are no cross-thread exceptions reported.

If you want to cause the exceptions to happen as normal, comment out the BindingOperations.EnableCollectionSynchronization call.

Summary

WPF 4.5 includes a number of key targeted performance and capability features, one of which is cross-thread collection change notification. Enabling the change notification is as simple as the inclusion of a lock object and a single function call. Versus the manual dispatching approach, this can be a real performance win, not to mention save you some coding time.

     

Source Code and Related Media

Download /media/82691/collectionsynchronizationexample.zip
posted by Pete Brown on Monday, January 16, 2012
filed under:      

14 comments for “WPF 4.5: Observable Collection Cross-Thread Change Notification”

  1. Jonathan Allen says:
    Another question.

    MainViewModel.Stocks has a public setter. If it does get set, wouldn't you need to call BindingOperations.EnableCollectionSynchronization on the new value?
  2. Petesays:
    @jonathan

    Missed the one about the setter. Yes, I would, because the collection instance changes.

    In this case, it should really be a private set, but it would still be good to have the code to enable synchronization inside that setter rather than just the VM constructor.

    Pete
  3. Paulussays:
    Hi Pete,

    EnableCollectionSynchronization(IEnumerable, Object, CollectionSynchronizationCallback) provides a callback. Do you have a practical use case for such feature?

    Thanks, Paulus
  4. MEKsays:
    Nice post and great that this will be in WPF 4.5 now we can remove a lot of those Dispatcher.BeginInvoke :-)

    It would be nice if AddRange was added to ObservableCollection but will it not be less important if we use EnableCollectionSynchronization ?
    In 4.0 we have to change to the UI thread and then call Add repeatedly to add several items which in turn updates the UI.
    But in 4.5 each call to add (yes AddRange would make our life easier here) will affect the UI thread whenever there is a switch between the threads. Does anyone agree that this makes AddRange a little less important or am I completely lost? :-)

    I do not know what the "in-place sorting and updating" post might be about but adding Sort to ObservableCollection would also be nice (if possible?).
  5. Petesays:
    @jonathan

    Looks like your assumptions are correct. I'll post a follow-up with better details about how this is actually working under the covers, but I'm taking my time on this one. Thanks for poking me :)

    @Paulus

    It's for when you want to have a more complex synchronization scheme rather than just a lock(someObject).

    @bx

    Powerpoint :)

    @MEK

    Agreed on wanting AddRange. It's still necessary as you really want one big collection update notification rather than a bunch of little ones.
  6. Anthonysays:
    This approach only takes care of the cross-thread notification problem but doesn't handle the thread-safety issues that arise. Try this collection which takes care of this problem as well as other multi-threaded problems that will inevitably crop up with other approaches : http://www.codeproject.com/Articles/64936/Multithreaded-ObservableImmutableCollection
  7. Willsetsays:
    Hi,

    Today I'll share with you the BEST service to [b]send anonymous emails[/b] with attachments,

    Check the link [url=http://sendanonymousemail.wordpress.com/]anonymous email[/url]

    Have fun, and don't be evil !

    W.

Comment on this Post

Remember me