I have a custom concurrent observable collection that I'm using as an ItemsSource in a WPF desktop application.
For the collection to be "oberservable" I implemented INotifyCollectionChanged. Since it is "concurrent", i.e. can be modified from multiple threads, I'm invoking the CollectionChanged event using System.Windows.Threading.Dispatcher (as suggested by the docs).
Because I want the UI elements to be updated live, e.g. re-sort the list when a property changes, (a.k.a. "live shaping"), I also implemented a ICollectionViewFactory to create the required view with its settings, e.g. SortDescriptions.
Consider the following code flow - all on the UI/dispatcher thread:
- I create the collection.
- I add items and raise the according
CollectionChangedevents. - I load a
Windowwith aListBoxand bind it to the collection.
I have three versions of a function which is called whenever the internal list (of my custom collection) is changed:
Version 1 (with CheckAccess and InvokeAsync)
private void _notify(NotifyCollectionChangedEventArgs args)
{
if (_dispatcher.CheckAccess())
{
CollectionChanged?.Invoke(this, args);
}
else
{
_dispatcher.InvokeAsync(() => CollectionChanged?.Invoke(this, args));
}
}
Version 2 (without CheckAccess and InvokeAsync)
private void _notify(NotifyCollectionChangedEventArgs args)
{
_dispatcher.InvokeAsync(() => CollectionChanged?.Invoke(this, args));
}
Version 3 (without CheckAccess and Invoke)
private void _notify(NotifyCollectionChangedEventArgs args)
{
_dispatcher.Invoke(() => CollectionChanged?.Invoke(this, args));
}
Version 1 & 3 work fine, but in Version 2 all items are displayed twice in the ´ListBox`.
It appears to be something like this:
- If I'm on the UI thread and I call
Dispatcher.InvokeAsync, the call is added to "the end of the UI message pump" - without the thread waiting for the result. - The UI element binds itself to the collection, creates the view and fills it's inner source with the added items.
- "Later", then, when the message pump is further processed, the dispatched events are raised and listened to and the
CollectionViewadds the items to its source, creating the duplicate entries.
And I (think I) understand that in Version 1 the events are fired (and waited for) before the UI element exists, so there are no issues regarding the CollectionView.
But why/how does Version 3 (with Invoke) work? The way the code behaves differently than when using InvokeAsync makes me think that it should dead-lock, because it waits for a call that should be processed "further down its own message pump", but it obviously doesn't. Does Invoke internally do some kind of CheckAccess?
Yes it can, you can find the details here