Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting thread access exceptions on OnCollectionChanged after Creators Update

In my program, I have an abstract class ObservableKeyedCollection<TKey, TItem> that inherits from KeyedCollection<TKey, TItem> and also implements INotifyCollectionChanged.

A realisation of this abstract class is bound to a ListBox. In this ListBox, I edit items on double click, and upon acceptance, I remove the old instance of the edited item from this ObservableKeyedCollection<TKey, TItem> realisation, and add the new instance that has been modified.

It all worked well before Windows 10 Creators Update (1703, build number 15063.250). Since the update, ObservableKeyedCollection<TKey, TItem> started throwing InvalidOperationExceptions with the following message:

The calling thread cannot access this object because a different thread owns it.

I do not use any async operations in this region of the code.

The whole stack trace would be too long but here is the top part starting with OnCollectionChanged:

at System.Windows.Threading.Dispatcher.VerifyAccess() at System.Windows.Threading.DispatcherObject.VerifyAccess() at System.Windows.DependencyObject.GetValue(DependencyProperty dp) at System.Windows.Controls.Primitives.Selector.GetIsSelected(DependencyObject element) at System.Windows.Controls.Primitives.Selector.ItemSetIsSelected(ItemInfo info, Boolean value) at System.Windows.Controls.Primitives.Selector.SelectionChanger.CreateDeltaSelectionChange(List'1 unselectedItems, List'1 selectedItems) at System.Windows.Controls.Primitives.Selector.SelectionChanger.End() at System.Windows.Controls.Primitives.Selector.RemoveFromSelection(NotifyCollectionChangedEventArgs e) at System.Windows.Controls.Primitives.Selector.OnItemsChanged(NotifyCollectionChangedEventArgs e) at System.Windows.Controls.ItemsControl.OnItemCollectionChanged2(Object sender, NotifyCollectionChangedEventArgs e) at System.Collections.Specialized.NotifyCollectionChangedEventHandler.Invoke(Object sender, NotifyCollectionChangedEventArgs e) at System.Windows.Data.CollectionView.OnCollectionChanged(NotifyCollectionChangedEventArgs args) at System.Windows.Controls.ItemCollection.OnViewCollectionChanged(Object sender, NotifyCollectionChangedEventArgs e) at System.Windows.WeakEventManager.ListenerList'1.DeliverEvent(Object sender, EventArgs e, Type managerType) at System.Windows.WeakEventManager.DeliverEventToList(Object sender, EventArgs args, ListenerList list) at System.Windows.WeakEventManager.DeliverEvent(Object sender, EventArgs args) at System.Collections.Specialized.CollectionChangedEventManager.OnCollectionChanged(Object sender, NotifyCollectionChangedEventArgs args) at System.Windows.Data.CollectionView.OnCollectionChanged(NotifyCollectionChangedEventArgs args) at System.Windows.Data.ListCollectionView.ProcessCollectionChangedWithAdjustedIndex(NotifyCollectionChangedEventArgs args, Int32 adjustedOldIndex, Int32 adjustedNewIndex) at System.Windows.Data.ListCollectionView.ProcessCollectionChanged(NotifyCollectionChangedEventArgs args) at System.Windows.Data.CollectionView.OnCollectionChanged(Object sender, NotifyCollectionChangedEventArgs args) at TetheredSun.ObservableKeyedCollection'2.OnCollectionChanged(NotifyCollectionChangedEventArgs e) at e:\Phil\Programozás\Modulok\TetheredSun.1.0\TetheredSun\ObservableKeyedCollection.cs, line number: 68 at TetheredSun.ObservableKeyedCollection`2.RemoveItem(Int32 index) at [...]

Edit 1:

Here is the offending code section that worked all right before Creators Update (an override of KeyedCollection<TKey, TItem>.RemoveItem(int index) ) :

protected override void RemoveItem(int index)
{
    TItem item = this[index];
    base.RemoveItem(index);
    if (deferNotifyCollectionChanged) return;
    if (item is IList) {
        // Listeners do not support multiple item changes, and our item happens to be an IList, so we must raise NotifyCollectionChangedAction.Reset.
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    } else {
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item));
    }
    OnPropertyChanged(new PropertyChangedEventArgs("Count"));
}

The problem seems to occur only if I invoke OnCollectionChanged with the NotifyCollectionChangedAction.Remove action. Replacing it with NotifyCollectionChangedAction.Reset seems to avert the exception:

protected override void RemoveItem(int index)
{
    TItem item = this[index];
    base.RemoveItem(index);
    if (deferNotifyCollectionChanged) return;
    // No exception thrown so far if I stick to NotifyCollectionChangedAction.Reset:
    OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    OnPropertyChanged(new PropertyChangedEventArgs("Count"));
}

I have tried to solve the problem with a Dispatcher as seen here: https://stackoverflow.com/a/22026686/2659699 but though my dispatcher is not null, its CheckAccess() evaluates to true, and I keep getting the same exception upon NotifyCollectionChangedEventHandler.Invoke().

Your thoughts and assistance are greatly appreciated.

like image 697
tethered.sun Avatar asked Nov 07 '22 22:11

tethered.sun


1 Answers

I had a similar problem and also after Win 10 creators update.

This wrapper class using BindingOperations.EnableCollectionSynchronization worked for me:

public class SynchronizedObservableCollection<T> : ObservableCollection<T>
{
    private readonly object _lockObject = new object();

    public SynchronizedObservableCollection()
    {
        Init();
    }

    public SynchronizedObservableCollection(List<T> list) : base(list)
    {
        Init();
    }

    public SynchronizedObservableCollection(IEnumerable<T> collection) : base(collection)
    {
        Init();
    }

    private void Init()
    {
        BindingOperations.EnableCollectionSynchronization(this, _lockObject);
    }
}
like image 148
Vizu Avatar answered Nov 14 '22 23:11

Vizu