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 InvalidOperationException
s 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.
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);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With