我有一个WPF应用程序,它通过WCF与服务器通信。 我在远程服务器上执行一个方法,回调方法初始化一个列表,其中包含在不同线程上运行的结果。 - 这很好,这正是我申请的目的。
但是,当我想在此列表中添加更多项目时,它会抛出一个异常,我无法从已初始化此列表的其他线程添加项目。
public ObservableCollection<ListBoxItemVM<T>> Items
{
get { return items; }
set
{
// This section runs on a separate thread.
items = value;
notify("Items");
if (allItems == null)
allItems = new ObservableCollection<ListBoxItemVM<T>>(items.Clone());
// I want to save the current context here and use it on the AddItem method
CollectionView view = (CollectionView)CollectionViewSource.GetDefaultView(Items);
view.Filter = searchFilter;
}
}
public void AddItem(ListBoxItemVM<T>
{
this.items.Add(item); // The following exception throws here
}
异常:这种类型的CollectionView不支持从与Dispatcher线程不同的线程更改其SourceCollection。
我正在寻找一些方法来保存列表初始化的线程(或线程的ExecuteContext),并使用此线程/上下文将项目添加到该列表中。
应该提到的是,UI线程没有任何关系,我处理了代码中另一个区域的UI线程的编组。
我试图用UI SynchronizationContext编组this.items.Add(item);
代码,购买它们是不同的,所以它失败了。
由于
答案 0 :(得分:1)
从.NET 4.5开始,有一个内置机制可自动同步对集合的访问并将CollectionChanged事件调度到UI线程。要启用此功能,您需要从UI线程中调用BindingOperations.EnableCollectionSynchronization。
EnableCollectionSynchronization做了两件事:
记住调用它的线程,并使数据绑定管道编组该线程上的CollectionChanged事件。 获取对集合的锁定,直到处理了编组事件,以便运行UI线程的事件处理程序在从后台线程修改集合时不会尝试读取集合。 非常重要的是,这并不能解决所有问题:为了确保对本质上不是线程安全的集合的线程安全访问,您必须通过在即将修改集合时从后台线程获取相同的锁来与框架协作。
因此,正确操作所需的步骤是:
决定您将使用哪种锁定
这将确定必须使用EnableCollectionSynchronization的哪个重载。大多数情况下,一个简单的锁定语句就足够了,所以这个重载是标准选择,但是如果你使用一些花哨的同步机制,那么也支持自定义锁。
创建集合并启用同步
根据所选的锁定机制,在UI线程上调用适当的重载。如果使用标准锁定语句,则需要提供锁定对象作为参数。如果使用自定义同步,则需要提供CollectionSynchronizationCallback委托和上下文对象(可以为null)。调用时,此委托必须获取自定义锁,调用传递给它的Action并在返回之前释放锁。
通过在修改之前锁定集合进行合作
当您要自行修改集合时,您还必须使用相同的机制锁定集合;在简单方案中传递给EnableCollectionSynchronization的同一个锁对象上执行此操作,或使用自定义方案中的相同自定义同步机制执行此操作。