Observable Collection многопоточность - PullRequest
1 голос
/ 12 апреля 2019

У меня есть приложение, в котором элементы добавляются в коллекции из нескольких потоков. Случайно я получаю

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 необходимо создавать в потоке пользовательского интерфейса? Кто-то может подтвердить, что это так?

Также вы можете заметить, что я получаю представление коллекции по умолчанию CollectionViewSource.GetDefaultView (collection)

Представление коллекции также создается в том же потоке и технически, как я понимаю, является источником проблемы.

Я пытался смоделировать добавление из разных потоков, создавая тысячи задач и изменяя коллекцию без ошибок, НО снова случайная ошибка появляется из ниоткуда, я проверял обе, где коллекция не была связана и связана с пользовательским интерфейсом.

Есть идеи?

трассировка стека

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 ]

1 голос
/ 12 апреля 2019

Один из лучших способов справиться с этим - отказаться от 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.

0 голосов
/ 12 апреля 2019

Как насчет реализации поточно-ориентированной оболочки 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);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...