【问题标题】:Observable Collection multithreading可观察集合多线程
【发布时间】:2019-04-12 22:09:27
【问题描述】:

我有一个应用程序,其中项目从多个线程添加到集合中。 随机我得到一个

This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread. at System.Windows.Data.CollectionView.OnCollectionChanged(Object sender, NotifyCollectionChangedEventArgs args)

现在集合是在一个类中创建的,类本身是在多个线程上创建的。

这是一个类示例

public class Example
{
    public Example()
    {
        BindingOperations.EnableCollectionSynchronization(collection, COLLECTION_LOCK);

        var defaultView = CollectionViewSource.GetDefaultView(collection);
        defaultView.SortDescriptions.Add(new SortDescription("SomeProperty", ListSortDirection.Ascending));

        if (defaultView is ICollectionViewLiveShaping liveShaping)
            liveShaping.IsLiveSorting = true;
    }

    private readonly object COLLECTION_LOCK = new object();
    private readonly ObservableCollection<object> collection = new ObservableCollection<object>();

    public ObservableCollection<object> Collection
    {
        get
        {
            return collection;
        }
    }

    private void AddItem(object item)
    {
        lock(COLLECTION_LOCK)
        {
            if(!Collection.Contains(item))
            {
                Collection.Add(item);
            }
        }
    }

    private void RemoveItem(object item)
    {
        lock (COLLECTION_LOCK)
        {
            if (Collection.Contains(item))
            {
                Collection.Remove(item);
            }
        }
    }
}

我正在使用 BindingOperations.EnableCollectionSynchronization 来允许跨线程操作并始终使用指定的锁来修改集合。 错误仍然随机出现。

我也尝试在访问集合时使用 BindingOperations.AccessCollection,但错误仍然随机发生。

MS 文档指出 ObservableCollection 必须在 UI 线程上创建?有人可以确认是这样吗?

您还可以注意到我得到了默认的集合视图 CollectionViewSource.GetDefaultView(collection)

集合视图也是在同一线程和技术上创建的,因为我了解它的问题根源。

我试图通过创建数千个任务并修改集合而不发生错误来模拟从不同线程添加,但再次随机错误突然弹出,我测试了集合未绑定和绑定到 UI 的地方。

有什么想法吗?

堆栈跟踪

System.NotSupportedException: This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread.
   at System.Windows.Data.CollectionView.OnCollectionChanged(Object sender, NotifyCollectionChangedEventArgs args)
   at System.Collections.Specialized.NotifyCollectionChangedEventHandler.Invoke(Object sender, NotifyCollectionChangedEventArgs e)
   at System.Collections.ObjectModel.ObservableCollection`1.OnCollectionChanged(NotifyCollectionChangedEventArgs e)
   at System.Collections.ObjectModel.ObservableCollection`1.RemoveItem(Int32 index)
   at System.Collections.ObjectModel.Collection`1.Remove(T item)
   at Manager.ViewModels.HostViewModelBase.RemoveUser(IUserMemberViewModel user)

集合视图标志是 System.Windows.Data.CollectionView.CollectionViewFlags.ShouldProcessCollectionChanged | System.Windows.Data.CollectionView.CollectionViewFlags.IsCurrentBeforeFirst | System.Windows.Data.CollectionView.CollectionViewFlags.IsCurrentAfterLast | System.Windows.Data.CollectionView.CollectionViewFlags.IsDynamic | System.Windows.Data.CollectionView.CollectionViewFlags.AllowsCrossThreadChanges | System.Windows.Data.CollectionView.CollectionViewFlags.CachedIsEmpty

并且 AllowsCrossThreadChanges 为真

【问题讨论】:

标签: c# wpf


【解决方案1】:

解决这个问题的最佳方法之一是完全放弃 ObservableCollection。它的用例非常狭窄,很难解决 Dispatcher 问题。

改用DynamicData - 一旦你掌握了它,它就会变得非常强大并且使用起来非常自然:

ReadOnlyObservableCollection<TradeProxy> data;
var source = new SourceCollection<YourClass>();

source.Connect()
    .Sort(SortExpressionComparer<YourClass>.Descending(t => t.SomeProperty)) 
    .ObserveOnDispatcher()          //ensure operation is on the UI thread
    .Bind(out data)         //Populate the observable collection
    .Subscribe();

// you can do that in ANY THREAD you want and the view will update without any problems:

source.Add(yourClasse);

DynamicData 还具有过滤功能,可以非常轻松地重新应用过滤器、分页、分组......等等。它基于 Rx,因此在处理大型集合时,您可以轻松地限制更改,然后在 UnitTests 中立即进行。

【讨论】:

    【解决方案2】:

    如何实现ObservableCollection 的线程安全包装器?

    public class ObservableCollectionWrapper<T> : ICollection<T>, INotifyCollectionChanged
    {
        private readonly ObservableCollection<T> _collection;
        private readonly Dispatcher _dispatcher;
    
        public event NotifyCollectionChangedEventHandler CollectionChanged;
    
        public ObservableCollectionWrapper(ObservableCollection<T> collection, Dispatcher dispatcher)
        {
            _collection = collection;
            _dispatcher = dispatcher;
            collection.CollectionChanged += Internal_CollectionChanged;
        }
    
        private void Internal_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            _dispatcher.Invoke(() =>
            {
                this.CollectionChanged?.Invoke(sender, e);
            });
        }
    
        public int Count => _collection.Count;
        /* Implement the rest of the ICollection<T> interface */
    }
    

    使用示例:

    var collectionWrapper = new ObservableCollectionWrapper<object>(collection, this.Dispatcher);
    var defaultView = CollectionViewSource.GetDefaultView(collectionWrapper);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-12-02
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多