Is an ObservableCollection not thread safe even in .NET 4.5?

I hit my head on the virtual wall for several days. The BindingOperations.EnableSynchronization method only works partially in .NET 4.5.

I wrote a test that sometimes fails:

object blah = new object(); Application app = Application.Current == null ? new Application() : Application.Current; SynchronizationContext.SetSynchronizationContext(new SynchronizationContext()); ObservableCollection<ThreadSafeObservableTestObject> collection = null; collection = new ObservableCollection<ThreadSafeObservableTestObject>(); BindingOperations.EnableCollectionSynchronization(collection, blah); CollectionTestWindow w = new CollectionTestWindow(); Task.Factory.StartNew(() => { Thread.Sleep(2000); w.TestCollection = collection; collection.CollectionChanged += collection_CollectionChanged; collection.Add(new ThreadSafeObservableTestObject() { ID = 1, Name = "Sandra Bullock" }); collection.Add(new ThreadSafeObservableTestObject() { ID = 2, Name = "Jennifer Aniston" }); collection.Add(new ThreadSafeObservableTestObject() { ID = 3, Name = "Jennifer Lopez" }); collection.Add(new ThreadSafeObservableTestObject() { ID = 4, Name = "Angelina Jolie" }); collection.Add(new ThreadSafeObservableTestObject() { ID = 5, Name = "Mary Elizabeth Mastrantonio" }); Thread.Sleep(5000); System.Windows.Application.Current.Dispatcher.Invoke(() => w.Close()); System.Windows.Application.Current.Dispatcher.Invoke(() => Application.Current.Shutdown()); }); app.Run(w); 

TestCollectionWindow is as follows:

  <ItemsControl ItemsSource="{Binding TestCollection}" Name="list"> <ItemsControl.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal"> <TextBlock Text="{Binding Name}" /> <TextBlock Text="{Binding ID}" /> </StackPanel> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> 

Nothing magical here. But the result is almost every time that some entries twice in the user interface are the same objects! The results window looks like this:

Sandra Bullock 1
Jennifer Aniston 2
Jennifer Lopez 3
Angelina Jolie 4
Mary Elizabeth Mastrantonio 5
Jennifer Aniston 2

As you can clearly see, Jennifer Aniston is listed twice. It can be easily reproduced. Is this a common problem or is there something wrong with this test, for example, incorrect application implementation?

Thank you in advance!

+5
c # wpf
May 12 '13 at 20:17
source share
3 answers

The class is documented so as not to be thread safe:

Thread safety
Any public static (Shared in Visual Basic) members of this type are thread safe. Any instance members do not guarantee thread safety.

No, this is not thread safe.

Note that BindingOperations.EnableCollectionSynchronization does not magically make the entire collection thread safe. It tells only the binding system that you intend to use the lock object to prevent multiple threads from accessing the collection at the same time.

Since you are not actually using the lock object, you can also not call this method, the results will be equally unpredictable.

Try creating a lock on the blah object around each statement that accesses the collection. Unfortunately, the information about data binding in WPF is unknown to me, so I have no idea if this is enough.

+11
May 12 '13 at 20:29
source share

I also recently had to solve this problem and wrote about my solution in CodeProject: http://www.codeproject.com/Tips/998619/Thread-Safe-ObservableCollection-T

The solution included using SyncronizationContext to call event handlers in the UI thread and ReaderWriterLockSlim to ensure that only one record was written at a time and that there was no record when reading.

The full source code is available at the CodeProject link above, but here are some snippets:

 public SynchronizedObservableCollection() { _context = SynchronizationContext.Current; } private void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { var collectionChanged = CollectionChanged; if (collectionChanged == null) { return; } using (BlockReentrancy()) { _context.Send(state => collectionChanged(this, e), null); } } public bool Contains(T item) { _itemsLock.EnterReadLock(); try { return _items.Contains(item); } finally { _itemsLock.ExitReadLock(); } } public void Add(T item) { _itemsLock.EnterWriteLock(); var index = _items.Count; try { CheckIsReadOnly(); CheckReentrancy(); _items.Insert(index, item); } finally { _itemsLock.ExitWriteLock(); } OnPropertyChanged("Count"); OnPropertyChanged("Item[]"); OnCollectionChanged(NotifyCollectionChangedAction.Add, item, index); } 
+5
Jun 08 '15 at 19:51
source share

Since you are using .Net 4.5, you can use collections compatible with streams, ConcurrentDictionary, ConcurrentBag, etc., depending on your needs: http://msdn.microsoft.com/en-us/library/dd997305.aspx

This article may also help: http://www.codeproject.com/Articles/208361/Concurrent-Observable-Collection-Dictionary-and-So

+2
May 12 '13 at 20:38
source share



All Articles