¿ObservableCollection no es seguro para subprocesos incluso en .NET 4.5?

Me estoy golpeando la cabeza contra la pared virtual durante días. El método BindingOperations.EnableSynchronization parece funcionar solo parcialmente en .NET 4.5.

Escribí una prueba que falla a veces:

object blah = new object(); Application app = Application.Current == null ? new Application() : Application.Current; SynchronizationContext.SetSynchronizationContext(new SynchronizationContext()); ObservableCollection collection = null; collection = new ObservableCollection(); 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); 

El TestCollectionWindow se ve así:

            

Así que nada de magia aquí. Pero el resultado es casi cada vez que algunas entradas son dos veces en la interfaz de usuario: ¡los mismos objetos! La ventana de resultados se ve así:

Sandra bockock 1
Jennifer aniston 2
Jennifer lopez 3
Angelina Jolie 4
Mary Elizabeth Mastrantonio 5
Jennifer aniston 2

Como se puede ver claramente, Jennifer Aniston aparece dos veces. Esto se puede reproducir fácilmente. ¿Es este un problema general o hay algún problema con esta prueba, como una instanciación de aplicación defectuosa?

¡Gracias de antemano!

Se documenta que la clase no es segura para subprocesos:

Seguridad del hilo
Cualquier miembro público estático (Compartido en Visual Basic) de este tipo es seguro para subprocesos. No se garantiza que ningún miembro de instancia sea seguro para subprocesos.

Así que no, no es seguro para subprocesos.

Tenga en cuenta que BindingOperations.EnableCollectionSynchronization no hace que toda la colección sea segura para subprocesos. Solo le indica al sistema de enlace qué objeto de locking desea utilizar para evitar que múltiples subprocesos accedan a la colección al mismo tiempo.

Dado que en realidad no está utilizando el objeto de locking, es mejor que no llame a ese método, los resultados serán igualmente impredecibles.

Intente emitir un lock en el objeto blah alrededor de cada statement que acceda a la colección. Desafortunadamente, los detalles sobre el enlace de datos en WPF me son desconocidos, así que no tengo idea de si eso es suficiente.

Hace poco también necesitaba resolver este problema y escribí sobre mi solución en CodeProject: http://www.codeproject.com/Tips/998619/Thread-Safe-ObservableCollection-T

La solución involucraba el uso de un SyncronizationContext para invocar los controladores de eventos en el hilo de la IU y un ReaderWriterLockSlim para asegurar que solo ocurriera una escritura a la vez y que no se produjeran escrituras durante una lectura.

El código fuente completo está disponible en el enlace de CodeProject anterior, pero aquí hay algunos fragmentos:

 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); } 

Ya que está utilizando .Net 4.5, puede usar las colecciones Thread-safe, ConcurrentDictionary, ConcurrentBag, etc., lo que se adapte a sus necesidades: http://msdn.microsoft.com/en-us/library/dd997305.aspx

Este artículo también puede ayudar: http://www.codeproject.com/Articles/208361/Concurrent-Observable-Collection-Dictionary-and-So