ObservableCollection n’est pas thread-safe même dans .NET 4.5?

Je frappe ma tête contre le mur virtuel depuis des jours maintenant. La méthode BindingOperations.EnableSynchronization ne semble fonctionner que partiellement dans .NET 4.5.

J’ai écrit un test qui échoue parfois:

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

La fenêtre TestCollectionWindow ressemble à ceci:

            

Donc, rien de magique ici. Mais le résultat est presque chaque fois que certaines entrées sont deux fois dans l’interface utilisateur – les mêmes objects! La fenêtre de résultat ressemble à ceci alors:

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

Comme vous pouvez le voir clairement, Jennifer Aniston est listée deux fois. Cela peut être reproduit facilement. S’agit-il d’un problème général ou y a-t-il un problème avec ce test, tel qu’une instanciation d’application défectueuse?

Merci d’avance!

La classe est documentée pour ne pas être thread-safe:

Sécurité du fil
Tous les membres statiques publics (partagés dans Visual Basic) de ce type sont thread-safe. Il n’est pas garanti que les membres d’instance soient thread-safe.

Donc non, ce n’est pas thread-safe.

Notez que BindingOperations.EnableCollectionSynchronization ne rend pas comme par magie la collection entière thread-safe. Il indique uniquement au système de liaison quel object de locking vous souhaitez utiliser afin d’empêcher plusieurs threads d’accéder à la collection simultanément.

Puisque vous n’utilisez pas réellement l’object de locking, vous pouvez également ne pas appeler cette méthode, les résultats seront également imprévisibles.

Essayez de lock l’object blah autour de chaque instruction ayant access à la collection. Malheureusement, les détails concernant la liaison de données dans WPF m’ont été inconnus, je ne sais donc pas si cela suffit.

J’ai récemment eu besoin de résoudre ce problème également et j’ai écrit ma solution sur CodeProject: http://www.codeproject.com/Tips/998619/Thread-Safe-ObservableCollection-T

La solution impliquait l’utilisation d’un SyncronizationContext pour appeler les gestionnaires d’événements sur le thread d’interface utilisateur et d’un ReaderWriterLockSlim pour garantir qu’une seule écriture se produisait à la fois et qu’aucune écriture ne s’était produite pendant une lecture.

Le code source complet est disponible sur le lien CodeProject ci-dessus, mais voici quelques extraits:

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

Puisque vous utilisez .Net 4.5, vous pouvez utiliser les collections Thread-safe, ConcurrentDictionary, ConcurrentBag, etc., selon vos besoins: http://msdn.microsoft.com/en-us/library/dd997305.aspx

Cet article peut également aider: http://www.codeproject.com/Articles/208361/Concurrent-Observable-Collection-Dictionary-and-So