Mac*_*iek 71 c# wpf multithreading observablecollection
我有一个ObservableCollection<A> a_collection;
该集合包含'n'个项目.每个项目A看起来像这样:
public class A : INotifyPropertyChanged
{
public ObservableCollection<B> b_subcollection;
Thread m_worker;
}
Run Code Online (Sandbox Code Playgroud)
基本上,它都连接到WPF列表视图+详细信息视图控件,它在单独的列表视图中显示所选项目的b_subcollection(双向绑定,propertychanged上的更新等).当我开始实现线程时,问题出现了.整个想法是让整个a_collection使用它的工作线程来"工作",然后更新它们各自的b_subcollections并让gui实时显示结果.
当我尝试它时,我得到一个例外,说只有Dispatcher线程可以修改ObservableCollection,并且工作停止了.
任何人都可以解释这个问题,以及如何解决它?
干杯
Jon*_*Jon 113
从.NET 4.5开始,有一个内置机制可以自动同步对集合的访问并将CollectionChanged
事件分派给UI线程.要启用此功能,您需要从UI线程中进行调用.BindingOperations.EnableCollectionSynchronization
EnableCollectionSynchronization
做两件事:
CollectionChanged
该线程上的事件.非常重要的是,这并不能解决所有问题:为了确保对本质上不是线程安全的集合的线程安全访问,您必须通过在即将修改集合时从后台线程获取相同的锁来与框架协作.
因此,正确操作所需的步骤是:
这将决定EnableCollectionSynchronization
必须使用哪个过载.大多数情况下,一个简单的lock
语句就足够了,所以这个重载是标准选择,但是如果你使用一些花哨的同步机制,那么也支持自定义锁.
根据所选的锁定机制,在UI线程上调用适当的重载.如果使用标准lock
语句,则需要提供锁定对象作为参数.如果使用自定义同步,则需要提供CollectionSynchronizationCallback
委托和上下文对象(可以是null
).调用时,此委托必须获取您的自定义锁,调用Action
传递给它并在返回之前释放锁.
当您要自己修改集合时,还必须使用相同的机制锁定集合; 使用lock()
在EnableCollectionSynchronization
简单方案中传递给的同一个锁定对象,或在自定义方案中使用相同的自定义同步机制执行此操作.
Jos*_*osh 67
从技术上讲,问题不在于您是从后台线程更新ObservableCollection.问题是当你这样做时,集合会在导致更改的同一个线程上引发其CollectionChanged事件 - 这意味着控件正在从后台线程更新.
为了在控件绑定到后台线程时从后台线程填充集合,您可能必须从头开始创建自己的集合类型以解决此问题.有一个更简单的选项可能会对你有用.
将添加调用发布到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());
Run Code Online (Sandbox Code Playgroud)
此方法将立即返回(在项目实际添加到集合之前),然后在UI线程上,该项目将添加到集合中,每个人都应该感到高兴.
然而,事实是,由于所有的跨线程活动,此解决方案可能会在重负载下陷入困境.一个更有效的解决方案是批量处理一堆项目并定期将它们发布到UI线程,这样您就不会跨每个项目的线程调用.
该BackgroundWorker的类实现的模式,使您可以通过汇报其进展ReportProgress后台操作过程中的方法.通过ProgressChanged事件在UI线程上报告进度.这可能是您的另一种选择.
Whi*_*eep 16
使用.NET 4.0,您可以使用这些单行:
.Add
Application.Current.Dispatcher.BeginInvoke(new Action(() => this.MyObservableCollection.Add(myItem)));
Run Code Online (Sandbox Code Playgroud)
.Remove
Application.Current.Dispatcher.BeginInvoke(new Func<bool>(() => this.MyObservableCollection.Remove(myItem)));
Run Code Online (Sandbox Code Playgroud)
后代的集合同步代码。这使用简单的锁定机制来启用集合同步。请注意,您必须在UI线程上启用集合同步。
public class MainVm
{
private ObservableCollection<MiniVm> _collectionOfObjects;
private readonly object _collectionOfObjectsSync = new object();
public MainVm()
{
_collectionOfObjects = new ObservableCollection<MiniVm>();
// Collection Sync should be enabled from the UI thread. Rest of the collection access can be done on any thread
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{ BindingOperations.EnableCollectionSynchronization(_collectionOfObjects, _collectionOfObjectsSync); }));
}
/// <summary>
/// A different thread can access the collection through this method
/// </summary>
/// <param name="newMiniVm">The new mini vm to add to observable collection</param>
private void AddMiniVm(MiniVm newMiniVm)
{
lock (_collectionOfObjectsSync)
{
_collectionOfObjects.Insert(0, newMiniVm);
}
}
}
Run Code Online (Sandbox Code Playgroud)