即使在.net 4.5中ObservableCollection也不是线程安全的
本文关键字:线程 安全 ObservableCollection net | 更新日期: 2023-09-27 18:13:09
这几天我一直在埋头苦干。BindingOperations。在。net 4.5中,enablesynsynchronization方法似乎只能部分工作。
我写了一个有时会失败的测试:
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是这样的:
<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>
没有什么神奇的。但结果是,几乎每次一些条目在UI中出现两次——都是相同的对象!结果窗口看起来像这样:
桑德拉·布洛克1
詹妮弗·安妮斯顿2
詹妮弗·洛佩兹3
安吉丽娜·朱莉4
玛丽·伊丽莎白·马斯特拉安东尼奥5
詹妮弗·安妮斯顿2
你可以清楚地看到Jennifer Aniston被列了两次。这可以很容易地复制。这是一个普遍的问题,还是这个测试有什么问题,比如有缺陷的应用程序实例化?
提前感谢!
这个类不是线程安全的:
线程安全
这种类型的任何公共静态(在Visual Basic中共享)成员都是线程安全的。不能保证任何实例成员都是线程安全的。
所以,它不是线程安全的。
注意BindingOperations。EnableCollectionSynchronization并不能神奇地使整个集合线程安全。它只告诉绑定系统你打算使用哪个锁定对象,以防止多个线程同时访问集合。
由于实际上没有使用锁定对象,因此不妨不调用该方法,因为结果同样不可预测。
尝试在访问集合的每个语句周围的blah
对象上发出lock
。不幸的是,我不知道WPF中数据绑定的细节,所以我不知道这是否足够。
我最近也需要解决这个问题,并在CodeProject上写下了我的解决方案:http://www.codeproject.com/Tips/998619/Thread-Safe-ObservableCollection-T
解决方案包括使用SyncronizationContext来调用UI线程上的事件处理程序和ReaderWriterLockSlim来确保一次只发生一次写操作,并且在读取过程中不发生写操作。
完整的源代码可在上面的CodeProject链接中获得,但这里有一些片段:
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);
}
因为你使用的是。net 4.5,你可以使用线程安全的集合,ConcurrentDictionary, ConcurrentBag等,根据你的需要:http://msdn.microsoft.com/en-us/library/dd997305.aspx
本文也可能有所帮助:http://www.codeproject.com/Articles/208361/Concurrent-Observable-Collection-Dictionary-and-So