如何通过工作线程更新ObservableCollection?
我有一个ObservableCollection<A> a_collection;
该集合包含“n”个项目。 每个项目A看起来像这样:
public class A : INotifyPropertyChanged { public ObservableCollection<B> b_subcollection; Thread m_worker; }
基本上,这是所有连线到一个WPF的listview +详细信息视图控制显示在一个单独的列表视图中select的项目(双向绑定,更新属性更新等)b_subcollection。 当我开始执行线程时,问题就出现了。 整个想法是让整个a_collection使用它的工作线程来“做功”,然后更新它们各自的b_subcollections并让gui实时显示结果。
当我尝试它时,我得到一个exception,说只有Dispatcher线程可以修改一个ObservableCollection,工作停下来。
任何人都可以解释这个问题,以及如何解决这个问题?
干杯
从技术上讲,问题不在于您正在从后台线程更新ObservableCollection。 问题是,当你这样做的时候,集合在引发变化的同一个线程上引发了它的CollectionChanged事件 – 这意味着控件正在被后台线程更新。
为了在绑定控件时从后台线程填充集合,您可能必须从头开始创build自己的集合types,以解决这个问题。 有一个更简单的选项,可以为你解决。
将添加调用发布到UI线程上。
public static void AddOnUI<T>(this ICollection<T> collection, T item) { Action<T> addMethod = collection.Add; Application.Current.Dispatcher.BeginInvoke( addMethod, item ); } ... b_subcollection.AddOnUI(new B());
这个方法将立即返回(在项目被实际添加到集合之前),然后在UI线程上,项目将被添加到集合,每个人都应该感到高兴。
然而事实是,由于所有的跨线程活动,这个解决scheme可能会在沉重的负载下陷入困境。 一个更有效的解决scheme将批量一堆项目,并定期发布到UI线程,以便您不会通过线程调用每个项目。
BackgroundWorker类实现了一个模式,允许您在后台操作期间通过ReportProgress方法报告进度。 进度通过ProgressChanged事件在UI线程上报告。 这可能是另一个select。
.NET 4.5的新选项
从.NET 4.5开始,有一个内置的机制来自动同步对CollectionChanged
访问,并将CollectionChanged
事件分派给UI线程。 要启用此function,您需要在UI线程中调用BindingOperations.EnableCollectionSynchronization
。
EnableCollectionSynchronization
有两件事情:
- 记住它从中调用的线程,并导致数据绑定pipe道在该线程上封送
CollectionChanged
事件。 - 获取对集合的locking,直到处理完编组事件,以便运行UI线程的事件处理程序在从后台线程修改集合时不会尝试读取集合。
非常重要的是, 这并不关心一切 :为了确保线程安全地访问一个本来不是线程安全的集合, 您必须通过在集合即将被修改时从后台线程获取相同的锁来与框架合作 。
因此正确操作所需的步骤是:
1.决定你将使用什么样的locking
这将确定必须使用EnableCollectionSynchronization
哪个超载。 大多数时候一个简单的lock
语句就足够了,所以这个重载是标准的select,但是如果你使用一些奇特的同步机制,那么也支持自定义locking 。
2.创build收集并启用同步
根据所选的locking机制, 在UI线程上调用相应的重载。 如果使用标准的lock
语句,则需要提供locking对象作为参数。 如果使用自定义同步,则需要提供一个CollectionSynchronizationCallback
委托和一个上下文对象(可以为null
)。 当被调用时,这个委托必须获得你的自定义锁,在返回之前调用传递给它的Action
并释放锁。
3.在修改之前通过locking集合进行合作
当您要自己修改集合时,还必须使用相同的机制locking集合; 使用简单场景中传递给EnableCollectionSynchronization
的相同锁对象上的lock()
或自定义场景中的相同自定义同步机制来执行此操作。
使用.NET 4.0,您可以使用这些单行程序:
.Add
Application.Current.Dispatcher.BeginInvoke(new Action(() => this.MyObservableCollection.Add(myItem)));
.Remove
Application.Current.Dispatcher.BeginInvoke(new Func<bool>(() => this.MyObservableCollection.Remove(myItem)));