22

I have two WPF applications "UI", "Debugger" and one ClassLibrary "BL". UI references to Debugger and BL. Debugger references to BL. I have collection in BL called MyCollection. UI app starts the Debugger app and Debugger binds to a collection MyCollection in BL. When I try changing the MyCollection collection from UI app I am getting exception.

A first chance exception of type 'System.NotSupportedException' occurred in PresentationFramework.dll

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

I was googling around and found this: BindingOperations.EnableCollectionSynchronization I can't figure out how to use it. I don't want to reference to any UI dlls from my BL project. Can anybody assist me on that?

Thanks for the help!

Dilshod
  • 3,189
  • 3
  • 36
  • 67

5 Answers5

65

All the examples I've seen on Stack Overflow for this get it wrong. You must lock the collection when modifying it from another thread.

On dispatcher (UI) thread:

_itemsLock = new object();
Items = new ObservableCollection<Item>();
BindingOperations.EnableCollectionSynchronization(Items, _itemsLock);

Then from another thread:

lock (_itemsLock)
{
    // Once locked, you can manipulate the collection safely from another thread
    Items.Add(new Item());
    Items.RemoveAt(0);
}

More information in this article: http://10rem.net/blog/2012/01/20/wpf-45-cross-thread-collection-synchronization-redux

Dan Busha
  • 3,723
  • 28
  • 36
Drew Noakes
  • 300,895
  • 165
  • 679
  • 742
  • I assume this will queue some collection update events until the lock is freed and they can be processed by the GUI, correct? – ed22 May 15 '21 at 12:06
  • Can you not just use the `Items` reference as the lock object? That way you wouldn't have to bring the `_itemsLock` reference around to wherever `Items` is being modified. – Deantwo Aug 05 '22 at 13:48
  • You could, but locking on public values increases the chances of deadlocks. – Drew Noakes Aug 07 '22 at 07:34
4

I am not sure if this will help but still you can give it a try.

Add a Property in Debugger which will hold the Collection from BL like

private ObservableCollection<string> _data = new ObservableCollection<string>();
private object _lock = new object();

public ObservableCollection<string> Data { get {return _data;} }

In the constructor just add the below line

BindingOperations.EnableCollectionSynchronization(_data, _lock);

this will above line will take care of thread safety.

Below is the example

ViewModel (Debugger)

internal class ViewModelClass : INotifyPropertyChanged
{
    private object _lock = new object ();
    private ObservableCollection<string> _data;

    public ObservableCollection<string> Data
    {
        get { return _data; }
        private set
        {
            _data = value;
            RaisePropertyChanged ("Data");
        }
    }

    private string _enteredText;
    public string EnteredText
    {
        get { return _enteredText; }
        set
        {
            _enteredText = value;
            _data.Add (value); RaisePropertyChanged ("EnteredText");
        }
    }

    private void RaisePropertyChanged (string name)
    {
        var pc = PropertyChanged;
        if (pc != null)
            pc (this, new PropertyChangedEventArgs (name));
    }

    public ViewModelClass ()
    {
        var _model = new ModelClass ();
        Data = _model.Data;
        _data.CollectionChanged += (s, e) => RaisePropertyChanged ("Data");
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

Model(BL)

internal class ModelClass
{
    private ObservableCollection<string> _data;

    public ObservableCollection<string> Data
    {
        get { return _data; }
        private set { _data = value; }
    }

    public ModelClass ()
    {
        _data = new ObservableCollection<string> { "Test1", "Test2", "Test3" };
    }
}

MainWindow.xaml.cs

public partial class MainWindow : Window
{
    public MainWindow ()
    {
        InitializeComponent ();
        this.DataContext = new ViewModelClass ();
    }
}

MainWindow.xaml

<Window x:Class="CollectionSynchronizationTest.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="MainWindow"
            Height="350"
            Width="525">
<StackPanel>
    <ComboBox IsEditable="True"
                        ItemsSource="{Binding Data}"
                        Text="{Binding EnteredText, Mode=TwoWay, UpdateSourceTrigger=LostFocus}" />
    <Button Content="Test" />
</StackPanel>

When the window loads just enter "SomeValue" in the ComboBox and then after pressing the Tab key you should find the new value in the ComboBox dropdown

Sandesh
  • 2,966
  • 1
  • 20
  • 34
  • 2
    What is the point of having NotifyPropertyChanged for ObservableCollection? – Dilshod Feb 13 '14 at 06:38
  • The `INotifyPropertyChanged` interface is used to notify the UI for _any_ data change in the ViewModel. Otherwise the `UI` will never get new values. – Sandesh Feb 13 '14 at 07:06
  • 2
    ObservableCollection does that for you. So you don't need to use it for ObservableCollections. – Dilshod Feb 13 '14 at 07:59
  • 3
    "BindingOperations.EnableCollectionSynchronization(_data, _lock);" doesn't take care of thread safety. You still have to lock the _lock object. – claudekennilol Mar 24 '16 at 15:33
  • The _lock is used internally to synchronize the access to the IEnumerable object. There would be no point in using the BindingOperation if **you** had to use an explicit lock statement again – Sandesh Mar 25 '16 at 13:03
  • Why do you have to pass a lock in? That suggests you are supposed to also lock with it from other threads too, no? Otherwise the collection could just allocate the lock internally. There's no clear indication of this in the documentation. – Drew Noakes Oct 11 '16 at 12:27
  • 3
    You do have to lock on the lock object when modifying the collection from another thread (you just don't have to dispatch to the UI thread to do it). See this article for more info: http://10rem.net/blog/2012/01/20/wpf-45-cross-thread-collection-synchronization-redux – Drew Noakes Oct 11 '16 at 12:38
  • 1
    I don't see the line `BindingOperations.EnableCollectionSynchronization(_data, _lock);` anywhere in the sample code. (Yet that was the entire point of this example.) it could be added in constructor `public ViewModelClass ()`, after `Data = _model.Data;`. OR it could be put into Data's setter: `if (value != null) BindingOperations.EnableCollectionSynchronization(value, _lock);` – ToolmakerSteve Apr 18 '20 at 00:00
  • 3
    @claudekennilol is correct: *your* code *must* do `lock(_lock){ ... }` around any accesses *you* do to the ObservableCollection. All that `EnableCollectionSynchronization` does is make WPF lock *its* code, using the same lock as you. This sample code is (dangerously) incomplete. – ToolmakerSteve Apr 18 '20 at 00:01
0

A WPF application can display a collection of data using an ItemsControl or one of its subclasses (ListBox, DataGrid, TreeView, ListView, etc.). WPF channels all its access to the collection through a subclass of CollectionView. Both the ItemsControl and the CollectionView have affinity to the thread on which the ItemsControl was created, meaning that using them on a different thread is forbidden and throws an exception. In effect, this restriction applies to the collection as well. You may want to use the collection on multiple threads. For example, you want to update the collection (add or remove items) on a "data-gathering" thread, while displaying the results on a "user interface" thread, so that the UI remains responsive while data-gathering is happening. In such a situation, you are responsible for ensuring synchronized ("thread-safe") access to the collection. This is typically done using either a simple lock mechanism or a more elaborate synchronization mechanism such as semaphores, reset events, etc. While you must synchronize your application's access to the collection, you must also guarantee that access from WPF (specifically from CollectionView) participates in the same synchronization mechanism. You do this by calling the EnableCollectionSynchronization method.

The DOC remark this very nice, I think you should have a look: https://learn.microsoft.com/en-us/dotnet/api/system.windows.data.bindingoperations.enablecollectionsynchronization?view=netcore-3.1

Shannon
  • 54
  • 1
  • 11
-2

In this blog you find an easy tutorial how to work with BindingOperations...it is quite easy.

Marco
  • 2,189
  • 5
  • 25
  • 44
-4

I could not figure out how to use it, either, when I had the same problem.

I ended with my own collection type where I store the dispatcher and use it when necessary. Note that my naming was very poor, this collection is not threadsafe, far from it.

public class ThreadableObservableCollection<T> : ObservableCollection<T>
{
    private readonly Dispatcher _dispatcher;
    public ThreadableObservableCollection()
    {
      _dispatcher = Dispatcher.CurrentDispatcher;
    }

    public void ThreadsafeRemove(T item, Action callback)
    {
      if (_dispatcher.CheckAccess())
      {
        Remove(item);
        callback();
      }
      else
      {
        _dispatcher.Invoke(() =>
          {
            Remove(item);
            callback();
          });
      }
    }

    public void ThreadsafeInsert(int pos, T item, Action callback)
    {
      if (_dispatcher.CheckAccess())
      {
        Insert(pos, item);
        callback();
      }
      else
      {
        _dispatcher.Invoke(() =>
          {
            Insert(pos, item);
            callback();
          });
      }
    }
  }
Herm
  • 2,956
  • 19
  • 32
  • This doesn't use the technique asked about in the question (`BindingOperations.EnableCollectionSynchronization`). – Drew Noakes Oct 11 '16 at 12:29