2013-05-12 27 views
5

Tôi đập đầu vào tường ảo trong nhiều ngày. Phương thức BindingOperations.EnableSynchronization dường như chỉ hoạt động một phần trong .NET 4.5.ObservableCollection không phải là chủ đề an toàn ngay cả trong .NET 4.5?

Tôi đã viết một bài kiểm tra đó không đôi khi:

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

Các TestCollectionWindow trông như thế này:

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

Vì vậy, không có gì kỳ diệu ở đây. Nhưng kết quả là hầu như mọi khi một số mục nhập hai lần trong giao diện người dùng - cùng một đối tượng! Cửa sổ kết quả sẽ như thế này thì:

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

Như bạn có thể thấy rõ ràng Jennifer Aniston được liệt kê hai lần. Điều này có thể được sao chép dễ dàng. Đây có phải là vấn đề chung hay không có bất kỳ điều gì sai trái với bài kiểm tra này, chẳng hạn như một bản tóm tắt ứng dụng không đúng?

Cảm ơn bạn trước!

+1

Hầu hết các lớp học tập không phải là thread an toàn và sẽ không bao giờ. An toàn chủ đề là ** cứng ** và yêu cầu các API khác nhau. Nhìn vào các bộ sưu tập đồng thời. – SLaks

+1

Có lẽ bạn nên kiểm tra không gian tên ['System.Collections.Concurrent'] (http://msdn.microsoft.com/en-us/library/system.collections.concurrent.aspx). –

+0

Hãy thử bộ sưu tập này để giải quyết vấn đề này cũng như các vấn đề đa luồng khác chắc chắn sẽ nảy sinh với các cách tiếp cận khác: http://www.codeproject.com/Articles/64936/Multithreaded-ObservableImmutableCollection – Anthony

Trả lời

11

Lớp là documented không được thread-safe:

Chủ đề An toàn
Bất kỳ public static (chung trong Visual Basic) thành viên của loại này là chủ đề an toàn. Bất kỳ thành viên cá thể nào cũng không được bảo đảm là luồng an toàn.

Vì vậy, không, không an toàn chỉ.

Lưu ý rằng BindingOperations.EnableCollectionSynchronization không kỳ diệu làm cho toàn bộ bộ sưu tập an toàn. Nó chỉ cho hệ thống ràng buộc khóa đối tượng mà bạn dự định sử dụng để ngăn chặn nhiều luồng truy cập vào bộ sưu tập cùng một lúc.

Vì bạn không thực sự sử dụng đối tượng khóa, bạn cũng có thể không gọi phương thức đó, kết quả sẽ không thể đoán trước được.

Thử phát hành lock trên đối tượng blah xung quanh mỗi câu lệnh truy cập bộ sưu tập. Thật không may các chi tiết xung quanh dữ liệu ràng buộc trong WPF là không rõ với tôi, vì vậy tôi không có ý tưởng nếu đó là đủ.

+2

Sử dụng 'lock() {} 'sẽ không hoạt động vì luồng khác là GUI. –

+2

Tôi nghĩ rằng đó là điểm của phương pháp EnableCollectionSynchronization, để nói cho hệ thống ràng buộc (GUI) rằng nó cần phải đồng bộ hóa trên đối tượng khóa, nhưng như tôi đã nói, tôi không có chuyên gia về điều này. –

2

Vì bạn đang sử dụng .Net 4.5, bạn có thể sử dụng các bộ sưu tập Thread-an toàn, ConcurrentDictionary, ConcurrentBag vv, nào phù hợp với nhu cầu của bạn: http://msdn.microsoft.com/en-us/library/dd997305.aspx

Bài viết này cũng có thể giúp: http://www.codeproject.com/Articles/208361/Concurrent-Observable-Collection-Dictionary-and-So

+0

Liên kết mã hóa có vẻ hữu ích, tôi sẽ xem xét ở đó. –

+1

Trên thực tế, bộ sưu tập mã hóa cũng không an toàn. Hãy thử http://www.codeproject.com/Articles/64936/Multithreaded-ObservableImmutableCollection – Anthony

+0

Xem câu trả lời của tôi cho giải pháp thay thế khác cũng được đăng lên CodeProject –

5

thời gian gần đây tôi cần thiết để giải quyết vấn đề này như là tốt và đã viết về tôi giải pháp trên CodeProject: http://www.codeproject.com/Tips/998619/Thread-Safe-ObservableCollection-T

các giải pháp liên quan đến sử dụng một SyncronizationContext để gọi xử lý sự kiện trên thread UI và một ReaderWriterLockSlim để đảm bảo chỉ có một ghi xảy ra tại một thời điểm và không viết ra trong một đọc.

mã nguồn đầy đủ có sẵn tại liên kết CodeProject trên nhưng đây là một số đoạn:

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

Đọc bài viết về Dự án Mã của bạn, và tôi phải nói đó là công việc tuyệt vời! – Contango

Các vấn đề liên quan