线程化和创建 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.
上实现
所以单步执行:
- 在
Settings.Modify
中,你执行collection = new ObservableCollection<Something>();
。这成功更新了 属性。 WPF 绑定系统会收到此更改的通知(通过假定的 INotifyPropertyChanged
接口)并为您处理线程更改。因此 ItemsControl.ItemsSource
通过绑定在 UI 线程上更新为 collection
. 的新值
ItemsControl
使用这个新的 ObservableCollection
并创建一个 CollectionView
用作其项目的数据源。
- 回到
Settings.Modify
,你打电话给 collection.Add(...)
。这成功地向 ObservableCollection<Something>
添加了一个项目,触发了它的 INotifyCollectionChanged.CollectionChanged
事件。
ItemsControl
创建的 CollectionView
处理 CollectionChanged
事件,但仍在另一个线程上,即它被引发的地方 .正如异常所述,CollectionView
不“支持从不同于 Dispatcher 线程的线程对其 SourceCollection 进行更改”。所以它抛出异常。此异常 看起来 像是来自 collection.Add
,但如果您查看调用堆栈,您会发现它实际上来自更深的许多帧。 collection.Add
只是涉及的 您的 代码的最深层次。
在使用 ObservableCollection
s 和多线程时,我建议在后台线程上创建完整集合(如果可能),然后再将完整集合传回 UI 线程进行绑定.在您的示例中,您可以创建一个局部变量,将您的项目添加到该变量,然后在所有项目就位后将其设置为您的 collection
属性。或者,您可以使用 Dispatcher.Invoke
或 Task<Something>
.
将单个项目传回要添加的 UI 线程
我想要一些关于 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.
所以单步执行:
- 在
Settings.Modify
中,你执行collection = new ObservableCollection<Something>();
。这成功更新了 属性。 WPF 绑定系统会收到此更改的通知(通过假定的INotifyPropertyChanged
接口)并为您处理线程更改。因此ItemsControl.ItemsSource
通过绑定在 UI 线程上更新为collection
. 的新值
ItemsControl
使用这个新的ObservableCollection
并创建一个CollectionView
用作其项目的数据源。- 回到
Settings.Modify
,你打电话给collection.Add(...)
。这成功地向ObservableCollection<Something>
添加了一个项目,触发了它的INotifyCollectionChanged.CollectionChanged
事件。 ItemsControl
创建的CollectionView
处理CollectionChanged
事件,但仍在另一个线程上,即它被引发的地方 .正如异常所述,CollectionView
不“支持从不同于 Dispatcher 线程的线程对其 SourceCollection 进行更改”。所以它抛出异常。此异常 看起来 像是来自collection.Add
,但如果您查看调用堆栈,您会发现它实际上来自更深的许多帧。collection.Add
只是涉及的 您的 代码的最深层次。
在使用 ObservableCollection
s 和多线程时,我建议在后台线程上创建完整集合(如果可能),然后再将完整集合传回 UI 线程进行绑定.在您的示例中,您可以创建一个局部变量,将您的项目添加到该变量,然后在所有项目就位后将其设置为您的 collection
属性。或者,您可以使用 Dispatcher.Invoke
或 Task<Something>
.