线程化和创建 ObservableCollection

Threading and creating ObservableCollection

我想要一些关于 C# (WPF) 在代码背后如何工作的解释。 情况(为简单起见,显然是假设的):

在我们的 UI 线程上,我们创建了 Settings 对象。它有一个变量:

public ObservableCollection<Something> collection;

从不同的后台线程,我们调用

try{
    Settings.Modify()
}

然后执行以下操作:

Settings.Modify()
{
    collection = new ObservableCollection<Something>();
    collection.Add(...)
}

现在,当然这会在 Add 函数中引发异常:

This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread.

但是collection = new ObservableCollection();行执行。据我所知,这个新集合是在后台线程的堆栈上创建的。 WPF UI 绑定到这个变量:

<ItemsControl ItemsSource="{Binding Settings.collection}"

据我了解,UI 线程应该无法再访问此集合。如果它尝试会发生什么,它会因为某种原因阻塞 UI 线程吗?

其他问题:为什么编译器让我将一个明显属于 UI 线程的对象替换为后台线程中的另一个对象,即使它知道我在尝试添加时搞砸了从另一个线程到那个集合。

编辑: 此问题的解决方案已得到解答 here:

这个我还很好奇的是:如果

collection = new ObservableCollection<Something>();

函数被单独调用,如果UI线程试图访问这个集合会做什么?

问题根本不是 ObservableCollection<Something>,而是 <ItemsControl ItemsSource="{Binding Settings.collection}"

ObservableCollection 本身在跨多个线程访问时没有任何问题(只要这些线程一次访问一个)。普通(非DependencyProperty)属性也是如此——它们不继承地属于单个线程。 WPF 绑定系统(在大多数情况下)可以处理对来自其他线程的绑定属性的更改(前提是您没有使用 DependencyProperty)。 不能 处理来自其他线程的更改,是 ItemsControl

让我们一步一步来,但首先我必须做一些假设。首先,我必须假设 collection 实际上是一个 属性,而不是一个字段(或者至少通过 属性 公开),因为您不能绑定到一个字段。其次,我必须假设涉及 INotifyPropertyChanged 接口,可能在 Settings class.

上实现

所以单步执行:

  1. Settings.Modify中,你执行collection = new ObservableCollection<Something>();。这成功更新了 属性。 WPF 绑定系统会收到此更改的通知(通过假定的 INotifyPropertyChanged 接口)并为您处理线程更改。因此 ItemsControl.ItemsSource 通过绑定在 UI 线程上更新为 collection.
  2. 的新值
  3. ItemsControl 使用这个新的 ObservableCollection 并创建一个 CollectionView 用作其项目的数据源。
  4. 回到 Settings.Modify,你打电话给 collection.Add(...)。这成功地向 ObservableCollection<Something> 添加了一个项目,触发了它的 INotifyCollectionChanged.CollectionChanged 事件。
  5. ItemsControl 创建的 CollectionView 处理 CollectionChanged 事件,但仍在另一个线程上,即它被引发的地方 .正如异常所述,CollectionView 不“支持从不同于 Dispatcher 线程的线程对其 SourceCollection 进行更改”。所以它抛出异常。此异常 看起来 像是来自 collection.Add,但如果您查看调用堆栈,您会发现它实际上来自更深的许多帧。 collection.Add 只是涉及的 您的 代码的最深层次。

在使用 ObservableCollections 和多线程时,我建议在后台线程上创建完整集合(如果可能),然后再将完整集合传回 UI 线程进行绑定.在您的示例中,您可以创建一个局部变量,将您的项目添加到该变量,然后在所有项目就位后将其设置为您的 collection 属性。或者,您可以使用 Dispatcher.InvokeTask<Something>.

将单个项目传回要添加的 UI 线程