ObservableCollection即使在.NET 4.5中也不是线程安全的?

Jen*_*Mig 5 .net c# wpf

我现在正在撞击虚拟墙几天.BindingOperations.EnableSynchronization方法似乎只在.NET 4.5中起作用.

我写了一个有时失败的测试:

        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);
Run Code Online (Sandbox Code Playgroud)

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>
Run Code Online (Sandbox Code Playgroud)

所以没有什么神奇的.但结果几乎每次都有一些条目在UI中两次 - 相同的对象!结果窗口如下所示:

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

你可以清楚地看到Jennifer Aniston被列入两次.这可以很容易地再现.这是一个普遍的问题,还是这个测试有什么问题,例如应用程序实例化有缺陷?

先感谢您!

ang*_*son 13

记录该类不是线程安全的:

线程安全
此类型的任何公共静态(在Visual Basic中为Shared)成员都是线程安全的.任何实例成员都不保证是线程安全的.

所以不,它不是线程安全的.

请注意,BindingOperations.EnableCollectionSynchronization不会神奇地使整个集合成为线程安全的.它只告诉绑定系统您打算使用哪个锁定对象,以防止多个线程同时访问该集合.

由于您实际上并未使用锁定对象,因此您也可以不调用该方法,结果将同样无法预测.

尝试lockblah访问集合的每个语句周围发出一个对象.不幸的是,我不了解WPF中数据绑定的细节,所以我不知道这是否足够.

  • 我认为这是EnableCollectionSynchronization方法的要点,告诉绑定系统(GUI)需要在锁定对象上进行同步,但正如我所说,我不是这方面的专家. (3认同)
  • 使用`lock(){}`将不起作用,因为另一个线程是GUI. (2认同)
  • @HenkHolterman - 根据文档,在使用采用锁定对象参数的“BindingOperations.EnableCollectionSynchronization”变体时,使用“lock(){}”正是您应该做的事情。为什么说这“行不通”?当然,程序员有责任仅对快速代码片段进行“lock()”,以避免导致 UI 延迟。例如,完成所有准备工作,然后*然后*仅锁定“Add”调用。或者,如果需要 foreach 一些昂贵的代码,请在制作集合副本时锁定,然后在副本上执行昂贵的代码。 (2认同)

Cor*_*ton 7

我最近也需要解决这个问题,并在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);
}
Run Code Online (Sandbox Code Playgroud)

  • 阅读您的Code Project文章,我必须说这是出色的工作! (2认同)