可观察的集合多线程

时间:2019-04-12 09:55:10

标签: c# wpf

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

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文档指出必须在UI线程上创建ObservableCollection吗?有人可以确认情况吗?

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

在我了解问题根源的同时,从技术上讲,收集视图也是在同一线程上创建的。

我试图通过创建数千个任务并修改集合而不会发生任何错误来模拟从不同线程进行添加,但是再次随机弹出错误,我测试了集合未绑定和绑定到UI的情况。 / p>

有什么想法吗?

堆栈跟踪

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

2 个答案:

答案 0 :(得分: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中将其立即生效。

答案 1 :(得分:0)

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

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 */
}