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

时间:2019-01-04 01:05:10

标签: c# wpf multithreading inotifycollectionchanged

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

为了使集合“可观察”,我实现了INotifyCollectionChanged。由于它是“并发的”,即可以从多个线程进行修改,因此我正在使用CollectionChanged调用System.Windows.Threading.Dispatcher事件(如文档所建议)。

因为我希望UI元素能够实时更新,例如当属性更改时(也称为“实时成形”)对列表进行重新排序,我还实现了ICollectionViewFactory来使用其设置创建所需的视图,例如SortDescriptions

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

  • 我创建了收藏集。
  • 我添加项目并引发相应的CollectionChanged事件。
  • 我用Window加载了ListBox并将其绑定到集合。

我有一个函数的三个版本,每当(我的自定义集合)的内部列表更改时,该函数就会被调用:

版本1 (带有CheckAccessInvokeAsync

    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中,所有项目在“列表框”中显示两次。

似乎是这样的:

  • 如果我在UI线程上并且调用Dispatcher.InvokeAsync,则该调用将添加到“ UI消息泵的末尾”,而无需线程等待结果。
  • UI元素将自身绑定到集合,创建视图,并使用添加的项目填充其内部源。
  • “稍后”,当进一步处理消息泵时,将发出并监听已调度的事件,并且CollectionView将项目添加到其源中,从而创建重复的条目。

我(认为我)理解在版本1中,事件是在UI元素存在之前触发(并等待)的,因此与CollectionView无关。

但是为什么/ 如何第3版(带有Invoke)起作用?代码的行为方式不同于使用InvokeAsync时的行为方式,使我认为它应该死锁,因为它等待应该在“进一步的消息泵”下处理的调用,但显然不是。 Invoke是否在内部进行某种CheckAccess

1 个答案:

答案 0 :(得分:1)

  

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

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