2013-05-12 15 views
5

Sto sbattendo la testa contro il muro virtuale da giorni ormai. Il metodo BindingOperations.EnableSynchronization sembra funzionare solo in .NET 4.5.ObservableCollection non thread-safe anche in .NET 4.5?

ho scritto un test che non riesce a volte:

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

Il TestCollectionWindow assomiglia a questo:

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

Quindi nulla di magico qui. Ma il risultato è quasi ogni volta che alcune voci sono due volte nell'interfaccia utente - gli stessi oggetti! La finestra risultato appare come questo allora:

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

Come puoi vedere chiaramente, Jennifer Aniston è elencata due volte. Questo può essere riprodotto facilmente. Si tratta di un problema generale o c'è qualcosa di sbagliato in questo test, ad esempio una istanza di applicazione imperfetta?

Grazie in anticipo!

+1

maggior parte delle classi di raccolta non sono thread-safe e non lo saranno mai. La sicurezza del thread è ** difficile ** e richiede API diverse. Guarda le collezioni concorrenti. – SLaks

+1

Forse dovresti controllare lo spazio dei nomi ['System.Collections.Concurrent'] (http://msdn.microsoft.com/en-us/library/system.collections.concurrent.aspx). –

+0

Provate questa raccolta che si prende cura di questo problema e di altri problemi multi-thread che inevitabilmente emergeranno con altri approcci: http://www.codeproject.com/Articles/64936/Multithreaded-ObservableImmutableCollection – Anthony

risposta

11

La classe è documented di non essere thread-safe:

Thread Safety
statici pubblici (Shared in Visual Basic) di questo tipo sono thread-safe. Non è garantito che tutti i membri di istanza siano thread-safe.

Quindi no, non è thread-safe.

Nota che BindingOperations.EnableCollectionSynchronization non crea magicamente l'intera raccolta thread-safe. Indica al sistema di binding solo l'oggetto di blocco che si intende utilizzare per impedire che più thread accedano alla raccolta contemporaneamente.

Poiché non si sta effettivamente utilizzando l'oggetto di blocco, si potrebbe anche non chiamare tale metodo, i risultati saranno ugualmente imprevedibili.

Provare a emettere un lock sull'oggetto blah attorno ad ogni istruzione che accede alla raccolta. Sfortunatamente i dettagli sull'associazione dati in WPF sono sconosciuti, quindi non ho idea se sia sufficiente.

+2

Uso di 'lock() {} 'non funzionerà perché l'altro thread è la GUI. –

+2

Ho pensato che fosse il punto del metodo EnableCollectionSynchronization, per dire al sistema di binding (GUI) che doveva sincronizzarsi sull'oggetto lock, ma come ho detto, non sono esperto in questo. –

2

Dato che stai utilizzando .Net 4.5, è possibile utilizzare le collezioni thread-safe, ConcurrentDictionary, ConcurrentBag ecc, secondo le proprie esigenze: http://msdn.microsoft.com/en-us/library/dd997305.aspx

Questo articolo può anche aiutare: http://www.codeproject.com/Articles/208361/Concurrent-Observable-Collection-Dictionary-and-So

+0

Il link codeproject sembra essere utile, darò un'occhiata lì. –

+1

In realtà la raccolta codeproject non è thread-safe. Prova http://www.codeproject.com/Articles/64936/Multithreaded-ObservableImmutableCollection – Anthony

+0

Vedi la mia risposta per un'altra soluzione alternativa pubblicata anche su CodeProject –

5

Recentemente ho bisogno di risolvere questo problema come bene e ha scritto circa il mio soluzione sul CodeProject: http://www.codeproject.com/Tips/998619/Thread-Safe-ObservableCollection-T

la soluzione coinvolto utilizzando un SyncronizationContext per richiamare i gestori eventi sul thread UI e un ReaderWriterLockSlim garantire verificato sola scrittura alla volta e che non scrive verificati durante un'operazione di lettura.

Il codice sorgente completo è disponibile al link CodeProject sopra ma ecco alcuni frammenti:

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

Leggi il tuo articolo sul progetto del codice e devo dire che è un lavoro eccellente! – Contango