0

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 accordingCollectionChanged events.
  • I load a 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:

  • 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?

mike
  • 1,198
  • 9
  • 27

1 Answers1

2

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())
TheGeneral
  • 69,477
  • 8
  • 65
  • 107