Search code examples
c#wpfmultithreadinginotifycollectionchanged

Does Dispatcher.Invoke call CheckAccess internally?


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?


Solution

  • 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())