If you have ever bound an ObservableCollection<T> to a WPF control, you know how difficult it is to add or remove items from multiple threads. The UI expects events to be raised on the UI thread, and you must not modify the collection until the change event has been processed by the UI. This means your code blocks until the UI processes the events, which can slow down throughput when multiple threads are waiting.
I created a new collection called ConcurrentObservableCollection<T>. The idea is to maintain two linked collections: a thread-safe collection for use in the view model, and a bindable collection that can be safely bound to a control. When you modify the first collection, the change is applied immediately and also enqueued for the second collection. The second collection processes pending changes on the UI thread, avoiding any WPF threading issues. This requires slightly more memory, but that is the trade-off for an easy-to-use thread-safe observable collection.
Here are the main features of the thread-safe observable collection:
ConcurrentObservableCollection<T> is a thread-safe collection that you can manipulate in a view-model or the code-behind. You don't need to lock to access the collection from multiple threads.- You can safely bind
collection.AsObservable to a WPF control. This observable collection implements INotifyCollectionChanged, INotifyPropertyChanged and IReadOnlyList<T>, so it integrates well with WPF controls. - Any change to the collection is directly made to the thread-safe collection and will be replicated to the bind-able collection on the UI thread (using the
Dispatcher) - Any access to the bind-able collection must be done from the UI thread. While you should not manipulate the observable collection directly, some WPF controls such as the
DataGrid need to add items in this list.
Here's an overview of how it works when you add items to a ConcurrentObservableCollection<T>:
Initialize the collection ⇒ Both collections are empty

Add an item to the collection
- The collection contains 1 item
- There is 1 pending event (Add) waiting for the dispatcher to be processed,
Dispatcher.BeginInvoke is called - The bindable collection is still empty as the dispatcher has not run the synchronization method

Add a second item to the collection ⇒ The first collection contains 2 items, the bindable collection is still empty as the dispatcher has not run the synchronization method
- The collection contains 2 items
- There are 2 pending events (Add) waiting for the dispatcher to be processed
- The bindable collection is still empty as the dispatcher has not run the synchronization method

Eventually the dispatcher processes the pending items and applies the changes to the observable collection ⇒ Both collections contain the same data and the UI is updated

#How to use it?
Add the NuGet package Meziantou.Framework.WPF (NuGet, GitHub)
Create a new ConcurrentObservableCollection<T> in the code-behind or the view-model and bind the AsObservable property to a control
XAML
<Window x:Class="Meziantou.Framework.WPF.CollectionSamples.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Meziantou.Framework.WPF.CollectionSamples"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<local:SampleViewModel />
</Window.DataContext>
<StackPanel Orientation="Vertical">
<Button Command="{Binding AddItems}">Add items</Button>
<!-- ⚠️ Bind Items.AsObservable, not Items -->
<ListBox Grid.Row="1" ItemsSource="{Binding Items.AsObservable}" />
</StackPanel>
</Window>
C#
public class SampleViewModel
{
public SampleViewModel()
{
AddItems = new DelegateCommand(AddItemsImpl);
}
public ConcurrentObservableCollection<string> Items { get; } = new ConcurrentObservableCollection<string>();
public ICommand AddItems { get; }
private void AddItemsImpl()
{
Task.Run(() => Parallel.For(0, 1000, i =>
{
// No need to lock here as the collection is thread-safe
Items.Add($"Item {i}");
}));
}
}
Click the button and observe that everything works great!
That's it! Use the collection as you would any other thread-safe list. The methods are thread-safe, so you don't need to lock before adding or removing items from the collection, or when enumerating it.
Using this collection, you don't have to worry about the UI thread in the view-model or the code-behind!
Do you have a question or a suggestion about this post? Contact me!