Dispatcher.Invoke 是否在内部调用 CheckAccess?

Does Dispatcher.Invoke call CheckAccess internally?

我有一个自定义的并发可观察对象 collection,我在 WPF 桌面应用程序中用作 ItemsSource

为了使 collection 成为 "oberservable",我实施了 INotifyCollectionChanged。因为它是 "concurrent",即可以从多个线程修改,我正在使用 System.Windows.Threading.Dispatcher 调用 CollectionChanged 事件(按照文档的建议)。

因为我希望 UI 元素实时更新,例如re-sort 属性 更改时的列表(a.k.a。"live shaping"),我还实现了 ICollectionViewFactory 以使用其设置创建所需的视图,例如SortDescriptions.

考虑以下代码流 - 全部在 UI/dispatcher 线程上:

我有一个函数的三个版本,每当内部列表(我的自定义 collection)发生更改时调用该函数:

版本 1CheckAccessInvokeAsync

    private void _notify(NotifyCollectionChangedEventArgs args)
    {
        if (_dispatcher.CheckAccess())
        {
            CollectionChanged?.Invoke(this, args);
        }
        else
        {
            _dispatcher.InvokeAsync(() => CollectionChanged?.Invoke(this, args));
        }
    }

版本 2(没有 CheckAccessInvokeAsync

    private void _notify(NotifyCollectionChangedEventArgs args)
    {
        _dispatcher.InvokeAsync(() => CollectionChanged?.Invoke(this, args));
    }

版本 3(没有 CheckAccessInvoke

    private void _notify(NotifyCollectionChangedEventArgs args)
    {
        _dispatcher.Invoke(() => CollectionChanged?.Invoke(this, args));
    }

版本 1 和 3 工作正常,但在版本 2 中,所有项目都在“ListBox”中显示两次。

好像是这样的:

而且我(认为我)明白在版本 1 中,事件在 UI 元素存在之前被触发(并等待),因此没有关于 CollectionView 的问题。

但是为什么/ 版本 3(Invoke)如何工作?代码的行为方式与使用 InvokeAsync 时不同,这让我认为它应该 dead-lock,因为它等待应该处理的调用 "further down its own message pump",但它显然没有。 Invoke 是否在内部做某种 CheckAccess

Does Dispatcher.Invoke call CheckAccess internally?

可以,你可以找到详细信息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())