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.
- 我添加项目并引发相应的
CollectionChanged
事件。
- 我用
ListBox
加载 Window
并将其绑定到 collection。
我有一个函数的三个版本,每当内部列表(我的自定义 collection)发生更改时调用该函数:
版本 1(CheckAccess
和 InvokeAsync
)
private void _notify(NotifyCollectionChangedEventArgs args)
{
if (_dispatcher.CheckAccess())
{
CollectionChanged?.Invoke(this, args);
}
else
{
_dispatcher.InvokeAsync(() => CollectionChanged?.Invoke(this, args));
}
}
版本 2(没有 CheckAccess
和 InvokeAsync
)
private void _notify(NotifyCollectionChangedEventArgs args)
{
_dispatcher.InvokeAsync(() => CollectionChanged?.Invoke(this, args));
}
版本 3(没有 CheckAccess
和 Invoke
)
private void _notify(NotifyCollectionChangedEventArgs args)
{
_dispatcher.Invoke(() => CollectionChanged?.Invoke(this, args));
}
版本 1 和 3 工作正常,但在版本 2 中,所有项目都在“ListBox”中显示两次。
好像是这样的:
- 如果我在 UI 线程上调用
Dispatcher.InvokeAsync
,调用将添加到 "the end of the UI message pump" - 线程无需等待结果。
- UI 元素将自身绑定到 collection,创建视图并使用添加的项目填充它的内部源。
- "Later",然后,当进一步处理消息泵时,将引发并侦听分派的事件,并且
CollectionView
将项目添加到其源,创建重复的条目。
而且我(认为我)明白在版本 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())
我有一个自定义的并发可观察对象 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.
- 我添加项目并引发相应的
CollectionChanged
事件。 - 我用
ListBox
加载Window
并将其绑定到 collection。
我有一个函数的三个版本,每当内部列表(我的自定义 collection)发生更改时调用该函数:
版本 1(CheckAccess
和 InvokeAsync
)
private void _notify(NotifyCollectionChangedEventArgs args)
{
if (_dispatcher.CheckAccess())
{
CollectionChanged?.Invoke(this, args);
}
else
{
_dispatcher.InvokeAsync(() => CollectionChanged?.Invoke(this, args));
}
}
版本 2(没有 CheckAccess
和 InvokeAsync
)
private void _notify(NotifyCollectionChangedEventArgs args)
{
_dispatcher.InvokeAsync(() => CollectionChanged?.Invoke(this, args));
}
版本 3(没有 CheckAccess
和 Invoke
)
private void _notify(NotifyCollectionChangedEventArgs args)
{
_dispatcher.Invoke(() => CollectionChanged?.Invoke(this, args));
}
版本 1 和 3 工作正常,但在版本 2 中,所有项目都在“ListBox”中显示两次。
好像是这样的:
- 如果我在 UI 线程上调用
Dispatcher.InvokeAsync
,调用将添加到 "the end of the UI message pump" - 线程无需等待结果。 - UI 元素将自身绑定到 collection,创建视图并使用添加的项目填充它的内部源。
- "Later",然后,当进一步处理消息泵时,将引发并侦听分派的事件,并且
CollectionView
将项目添加到其源,创建重复的条目。
而且我(认为我)明白在版本 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())