简体   繁体   中英

Observable Collection multithreading

I have an application where items being added to collections from multiple threads. Randomly i get an

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)

Now the collections are created in a class , the classes themselves are created on multiple threads.

Here is a class example

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

I am using the BindingOperations.EnableCollectionSynchronization to allow cross thread operations and always use the specified lock to modify collection. Still the error comes up randomly.

I have also tried to use BindingOperations.AccessCollection when accessing the collection but the error still happens randomly.

The MS documentation states that ObservableCollection must be created on a UI thread? Can someone confirm that its the case?

Also you can notice that i get the default collection view CollectionViewSource.GetDefaultView(collection)

The collection view is also created on same thread and technically as i understand its the source of the problem.

I have tried to simulate adding from different threads by creating thousands of tasks and modifying the collection with no error happening BUT again randomly error pops up out of nowhere, i tested with both where collection was not bound and bound to UI.

Any ideas?

Stack trace

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)

The collection view flags are 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

and AllowsCrossThreadChanges is true

One of the best ways to deal with that is to ditch ObservableCollection alltoghether. Its use case is very narrow and it's hard to work around the Dispatcher issue.

Go with DynamicData instead - once you get the hang of it, it becomes very powerfull and so natural to use:

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 also has filtering with very easy reaplying of the filter, paging, grouping,.... so many things. It is based on Rx, so on top of that you can easily throtle the change when working with big sets, and then make it all instant in UnitTests.

How about implementing a thread safe wrapper of the 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 */
}

Usage example:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM