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:
CollectionChanged
events.Window
with a ListBox
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:
Dispatcher.InvokeAsync
, the call is added to "the end of the UI message pump" - without the thread waiting for the result.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
?
Does Dispatcher.Invoke call CheckAccess internally?
Yes it can, you can find the details here
public void Invoke(Action callback, DispatcherPriority priority, CancellationToken cancellationToken, TimeSpan timeout)
{
if(callback == null)
{
throw new ArgumentNullException("callback");
}
ValidatePriority(priority, "priority");
if( timeout.TotalMilliseconds < 0 &&
timeout != TimeSpan.FromMilliseconds(-1))
{
throw new ArgumentOutOfRangeException("timeout");
}
// Fast-Path: if on the same thread, and invoking at Send priority,
// and the cancellation token is not already canceled, then just
// call the callback directly.
if(!cancellationToken.IsCancellationRequested && priority == DispatcherPriority.Send && CheckAccess())