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
CollectionChanged
events. - I load a
Window
with aListBox
and 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
CollectionView
adds 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
?