5

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.

Community
  • 1
  • 1
tethered.sun
  • 87
  • 3
  • 14
  • 1
    We've experienced the exact same thing internally. Sad to not see any answers on this! Did you ever find an answer for this? :-) – Simon May 22 '17 at 11:08
  • @Simon Gustavsson: I have not found anything yet. I linked this page in an Insider survey, so Microsoft might be aware of it if they are thorough. At the moment I use the workaround above, that is, to use `NotifyCollectionChangedAction.Reset` wherever I used to apply `NotifyCollectionChangedAction.Remove`. It must be wasteful but at the list size I am using I cannot notice any performance hit. – tethered.sun May 22 '17 at 17:34
  • Ugh. I think I am seeing this issue. And I'm only seeing it on certain machines and not others ... – cplotts Sep 12 '17 at 21:20
  • 2
    Changing to a Reset action instead of a Remove action fixes our issue. – cplotts Sep 12 '17 at 21:31
  • We saw this issue on a Windows 7 machine too ... so for us, it does not seem completely related to the Windows 10 Creators Update. – cplotts Sep 12 '17 at 21:33
  • It might make sense to file a Microsoft Connect issue on this. It sure feels like a Microsoft bug. – cplotts Sep 12 '17 at 21:37
  • 1
    I just added a Microsoft Connect issue myself. We just observed a second instance of this error. https://connect.microsoft.com/VisualStudio/feedback/details/3141688 – cplotts Sep 28 '17 at 16:52
  • 1
    @cplotts: I think, I have the same issue. Changing the notification action from Remove to Replace seams to solve the same issue. More investigation is done on Monday. – Arthur Sep 29 '17 at 12:28
  • 1
    @Arthur Hopefully Microsoft contacts me today. I plan to try and create a test application for them to reproduce it. Is your issue also only on certain machines? Ours happens on some machines but not others, and I haven't been able to pin it down. – cplotts Sep 29 '17 at 13:54
  • @cplotts: This issue consistently appears on two machines I have access to, but I have not tested on others. – tethered.sun Sep 30 '17 at 12:32
  • 1
    I had the exactly same problem, and found it has nothing to do with Windows 10. Instead, the culprit is .NET 4.7 (which is bundled in "Creators Update"). – Alejandro Jul 23 '18 at 15:03

1 Answers1

1

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);
    }
}
Vizu
  • 1,791
  • 1
  • 14
  • 21