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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/16511643/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-13 08:46:41  来源:igfitidea点击:

ObservableCollection not thread-safe even in .NET 4.5?

c#.netwpf

提问by Jens Mig

I am banging my head against the virtual wall for days now. The BindingOperations.EnableSynchronization method seems to work only partial in .NET 4.5.

我的头撞在虚拟墙上好几天了。BindingOperations.EnableSynchronization 方法似乎只能在 .NET 4.5 中部分工作。

I wrote a test that fails sometimes:

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

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

The TestCollectionWindow looks like this:

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>

So nothing magic here. But the result is almost every time that some entries are twice in the UI - the same objects! The result window looks like this then:

所以这里没有什么神奇之处。但结果几乎每次都有一些条目在 UI 中出现两次——相同的对象!结果窗口如下所示:

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

桑德拉·布洛克 1
詹妮弗·安妮斯顿 2
詹妮弗·洛佩兹 3
安吉丽娜·朱莉 4
玛丽·伊丽莎白·马斯特兰托尼奥 5
詹妮弗·安妮斯顿 2

As you can clearly see Jennifer Aniston is listed twice. This can be reproduced easily. Is this a general problem or is there anything wrong with this test, such as a flawed application instantiation?

正如你可以清楚地看到詹妮弗安妮斯顿被列出两次。这可以很容易地重现。这是一个普遍问题还是这个测试有什么问题,例如有缺陷的应用程序实例化?

Thank you in advance!

先感谢您!

回答by Lasse V. Karlsen

The class is documentedto not be thread-safe:

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

Thread Safety
Any public static (Shared in Visual Basic) members of this type are thread safe. Any instance members are not guaranteed to be thread safe.

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

So no, it is not thread-safe.

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

Note that BindingOperations.EnableCollectionSynchronizationdoes not magically make the entire collection thread-safe. It only tells the binding system which locking object that you intend to use in order to prevent multiple threads accessing the collection at the same time.

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

Since you're not actually using the locking object, you might as well not call that method, the results will be equally unpredictable.

由于您实际上并未使用锁定对象,因此您最好不要调用该方法,结果同样不可预测。

Try issuing a lockon the blahobject around each statement that accesses the collection. Unfortunately the details around data binding in WPF is unknown to me, so I have no idea if that is enough.

尝试lockblah访问集合的每个语句周围的对象上发出 a 。不幸的是,我不知道 WPF 中有关数据绑定的详细信息,所以我不知道这是否足够。

回答by Cory Charlton

I recently needed to solve this issue as well and wrote about my solution on CodeProject: http://www.codeproject.com/Tips/998619/Thread-Safe-ObservableCollection-T

我最近也需要解决这个问题,并在 CodeProject 上写了我的解决方案:http: //www.codeproject.com/Tips/998619/Thread-Safe-ObservableCollection-T

The solution involved using a SyncronizationContextto invoke the event handlers on the UI thread and a ReaderWriterLockSlimto ensure only one write occurred at a time and that no writes occurred during a read.

该解决方案涉及使用SyncronizationContext调用 UI 线程上的事件处理程序和ReaderWriterLockSlim以确保一次仅发生一次写入,并且在读取过程中不发生写入。

Full source code is available at the CodeProject link above but here's some snippets:

上面的 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);
}

回答by christiandev

Since you're using .Net 4.5, you can use the Thread-safe collections, ConcurrentDictionary, ConcurrentBag etc, whichever suits your needs : http://msdn.microsoft.com/en-us/library/dd997305.aspx

由于您使用的是 .Net 4.5,您可以使用适合您需要的线程安全集合、ConcurrentDictionary、ConcurrentBag 等:http: //msdn.microsoft.com/en-us/library/dd997305.aspx

This article may also help : http://www.codeproject.com/Articles/208361/Concurrent-Observable-Collection-Dictionary-and-So

这篇文章也可能有帮助:http: //www.codeproject.com/Articles/208361/Concurrent-Observable-Collection-Dictionary-and-So