Quantcast
Viewing all articles
Browse latest Browse all 26

Multi-threading, ObservableCollection, Reactive Extensions

We are all familiar with the MVVM scenario where we add items to WPF control via an ObservableCollection in our ViewModel.

Here’s a quick set of simple code to demonstrate our starting point for this post.

<StackPanel>
    <ListBox ItemsSource="{Binding Items}" Margin="10" />
    <Button Content="Add Item" Command="{Binding AddItemCommand}" 
            Margin="10" HorizontalAlignment="Left" />
</StackPanel>
public class MainViewModel
{
    public ObservableCollection Items { get; private set; }
    public DelegateCommand AddItemCommand { get; private set; }

    public MainViewModel()
    {
        Items = new ObservableCollection();
        AddItemCommand = new DelegateCommand(OnAddItemCommand);
    }

    private void OnAddItemCommand(object obj)
    {
        Items.Add("testing");
    }
}

And as you’d expect, here is the output

Image may be NSFW.
Clik here to view.
simplevm1

Now, lets try and add a little multi-threading and see what happens

private void OnAddItemCommand(object obj)
{
    ThreadPool.QueueUserWorkItem(o => Items.Add("testing"));
}

As you can see, we are now adding to our ObservableCollection on a background thread, and when click our button, we get this error.

This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread.

The more experienced among you will recognise this issue, and know that controls, dependency properties and some other constructs (including ObservableCollection) can only be updated on the main UI thread.

So many of you will try this solution where you marshal the change on to the UI thread via the Dispatcher, which works in most scenarios

private void OnAddItemCommand(object obj)
{
    ThreadPool.QueueUserWorkItem(o => Dispatcher.CurrentDispatcher
        .Invoke(() => Items.Add("testing")));
}

However, you will still get the same error:

This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread.

You may think to yourself “Ive added to the collection via the Dispatcher thread, so why am i still getting exceptions?”. The reason you are still getting exceptions is because although you’ve added an item to the collection via the Dispatcher, the CollectionChanged event is called within the ObservableCollection on the background thread, and there is no way to avoid this in this kind of scenario.

A lot of solutions for this type of scenario include creating custom ObservableCollection classes that guarantee to throw the CollectionChanged event on the Dispatcher thread, but that only adds to your code base, and adds to the lines of code in your solution that need to be maintained and tested.

A simple alternative solution is to use Reactive Extensions.

Reactive Extensions (or Rx as they’re sometimes called) are a Microsoft Library that you can download and include in your project, that make utilising multi-threaded data-feeds in your app much easier and more reliable (I wont go into the full details here, there’s plenty of info online).

Although Reactive Extensions’ primary purpose is to ‘react’ to data in motion, we can use it here to solve our problem. Here is a modified ViewModel that makes use of Rx to avoid threading issues in this scenario.

public class MainViewModel
{
    public ObservableCollection<string> Items { get; private set; }
    public DelegateCommand AddItemCommand { get; private set; }
    private IObserver<string> itemsObserver; 

    public MainViewModel()
    {
        Items = new ObservableCollection<string>();
        AddItemCommand = new DelegateCommand(OnAddItemCommand);

        var observable = Observable.Create<string>(o =>
            {
                itemsObserver = o;
                return Disposable.Empty;
            });

        observable.ObserveOn(DispatcherScheduler.Current)
                  .Subscribe(o => Items.Add(o));
    }

    private void OnAddItemCommand(object obj)
    {
        ThreadPool.QueueUserWorkItem(o => 
            itemsObserver.OnNext("testing"));
    }
}

As you can see, the reactive extension implementation has allowed us to add items to our collection from a background thread without any errors.


Filed under: MVVM, Reactive Extensions, WPF, XAML Image may be NSFW.
Clik here to view.
Image may be NSFW.
Clik here to view.

Viewing all articles
Browse latest Browse all 26

Trending Articles