问题描述
我想对C#(WPF)在代码背后的工作方式进行一些解释。 情况(为简单起见,显然是假设的):
在我们的UI线程上,我们创建Settings对象。它具有一个变量:
public ObservableCollection<Something> collection;
try{
Settings.Modify()
}
然后执行以下操作:
Settings.Modify()
{
collection = new ObservableCollection<Something>();
collection.Add(...)
}
现在,这当然会在Add函数上引发异常:
这种类型的CollectionView不支持对其更改的更改 来自与dispatcher线程不同的线程的SourceCollection。
但是集合= new ObservableCollection();行执行。据我所知,这个新集合是在后台线程的堆栈上创建的。 WPF UI绑定到此变量:
<ItemsControl ItemsSource="{Binding Settings.collection}"
据我了解,UI线程应该不再能够访问此集合。如果尝试,会发生什么情况,由于某种原因会阻塞UI线程吗?
另一个问题:为什么编译器让我让一个明显属于UI线程的对象替换为后台线程中的另一个对象,即使它知道我在尝试从另一个线程添加到该集合时也搞砸了
对此我仍然感到好奇:如果
collection = new ObservableCollection<Something>();
解决方法
问题实际上根本不是 ObservableCollection<Something>
,而是 <ItemsControl ItemsSource="{Binding Settings.collection}"
。
ObservableCollection
本身在跨多个线程访问时没有任何问题(只要这些线程一次访问一个)。普通(非DependencyProperty
)属性也是如此-它们不继承地属于单个线程。 WPF 绑定系统可以(在大多数情况下)处理来自其他线程的绑定属性的更改(前提是您没有使用 DependencyProperty
)。 不能处理来自其他线程的更改的是ItemsControl
。
让我们逐步完成它,但首先我必须做出一些假设。首先,我必须假设 collection
实际上是一个属性,而不是一个字段(或者至少通过一个属性公开),因为您无法绑定到一个字段。其次,我必须假设涉及 INotifyPropertyChanged
接口,可能在 Settings
类上实现。
所以逐步完成:
- 在
Settings.Modify
中,您执行collection = new ObservableCollection<Something>();
。这成功地更新了属性。 WPF 绑定系统会收到有关此更改的通知(通过假定的INotifyPropertyChanged
接口)并为您处理线程更改。因此ItemsControl.ItemsSource
在 UI 线程上通过绑定更新为collection
的新值。 -
ItemsControl
使用这个新的ObservableCollection
并创建一个CollectionView
,将其用作其项目的数据源。 - 回到
Settings.Modify
,您调用collection.Add(...)
。这成功地向ObservableCollection<Something>
添加了一个项目,从而触发了它的INotifyCollectionChanged.CollectionChanged
事件。 -
CollectionView
创建的ItemsControl
处理CollectionChanged
事件,但仍在另一个线程上,在那里引发它。正如异常所说,CollectionView
不“支持从不同于 Dispatcher 线程的线程更改其 SourceCollection”。所以会抛出异常。这个异常看起来来自collection.Add
,但是如果您查看调用堆栈,您会发现它实际上来自更深的许多帧。collection.Add
只是所涉及的您代码的最深层次。
当使用 ObservableCollection
和多个线程时,我建议在将完整集合传回 UI 线程进行绑定之前,在后台线程上创建完整集合(如果可能)。在您的示例中,您可以创建一个局部变量,将您的项目添加到该变量中,然后在所有项目就位后将其设置为您的 collection
属性。或者,您可以使用 Dispatcher.Invoke
或 Task<Something>
将单个项目传递回要添加的 UI 线程。