如何以高效的方式将大量项目添加到数据绑定的ObservableCollection中
如何以高效的方式将大量项目添加到数据绑定的ObservableCollection中
之前的几个问题中提到了相关问题(见下文),但讨论的解决方案似乎都不适用于这种情况。此应用程序使用 ObservableCollection
通过数据绑定来驱动一个常规的 WPF ListBox
(使用一些自定义样式)。ObservableCollection 可能包含大量项,但由于 ListBox 默认以虚拟方式运行,这不是问题。(拥有数十万项的“静态”性能非常可观。)
然而,如果集合已经包含大量项,然后通过 ObservableCollection
添加了更多项,不可避免地由于所有的 NotifyCollectionChanged
事件被触发,UI 将暂时停止响应。在这种情况下,推荐的解决方法是对 ObservableCollection 进行子类化,并实现一个 AddRange()
方法,该方法将新元素添加到(受保护的)Items
集合中,然后使用 NotifyCollectionChangedAction.Reset
调用 NotifyCollectionChanged
。
根据我的经验,这解决了一个问题,但又引发了另一个问题。虽然不再引发大量的 Add
事件,但 Reset
通知会导致 ListBox 重新评估整个集合,如果集合包含数以万计的项,这可能需要相当长的时间。再加上目标应用程序中可能经常添加大量项,这意味着应用程序会在 ListBox 消化所有新信息的过程中长时间占用大量 CPU。
可以争论的是,事后看来 ObservableCollection
不是向用户提供如此大数据集的正确方式,但是既然已经走到这一步,很难看到一个简单的重新架构应用程序来解决此问题的方法。
非常感谢您提供的建议。
问题的出现原因:
直接将ObservableCollection绑定到UI,当向ObservableCollection中添加大量项时,会导致UI出现卡顿或冻结的问题。
解决方法:
不直接将ObservableCollection绑定到UI,而是将其绑定到ICollectionView。这样,在向ObservableCollection添加完项后,通过调用CollectionViewSource.GetDefaultView()来更新ICollectionView,从而更新UI的状态。
具体的解决方法如下所示:
Task.Factory.StartNew(() => SessionList = serviceAgent.GetSessions(someId, someStartDate)) .ContinueWith(t => Sessions.Add(SessionList), TaskScheduler.FromCurrentSynchronizationContext()) .ContinueWith(t => SessionsView = CollectionViewSource.GetDefaultView(Sessions), TaskScheduler.FromCurrentSynchronizationContext());
其中,Sessions是ObservableCollection,SessionsView是ICollectionView。这种方法可以在后台线程上获取记录,并且不会影响实际的基础集合,也不需要运行LINQ查询来显示结果集合。
此外,通过将Items Control绑定到ICollectionView,可以在不影响基础集合的情况下对视图进行多种操作。
通过将ObservableCollection绑定到ICollectionView而不是直接绑定到UI,可以避免在添加大量项时导致UI卡顿或冻结的问题。这种方法还可以在后台线程上获取记录,并且允许对视图进行多种操作。
问题的原因是在添加大量项到数据绑定的ObservableCollection时性能较差。通过进一步研究和思考启动行为,找到了解决这个问题的答案。
在应用程序启动时,能够在几秒钟内将200,000个项读入到ListBox中,因此它应该能够轻松地维持每秒100个添加的更新速度。根本问题是涉及到Dispatcher,因为新项的创建是在一个单独的线程中进行的。因此,将代码从这样的形式:
foreach (T item in items) { dispatcher.Invoke(DispatcherPriority.DataBind, (Action<T>)((item) => { Add(item); }), item); }
改为这样的形式:
dispatcher.Invoke(DispatcherPriority.DataBind, (Action<IEnumerable<T>>)((itms) => { foreach (T item in itms) { Add(item); } }), items);
即在Dispatcher中迭代要添加的项,而不是在外部迭代,对性能产生了巨大的影响,这意味着我不需要再调用Reset操作。
我可能不同意你提供的解决方案。如果UI中有一堆数据绑定与之绑定,需要在这些被添加的集合之前显示出来,那么Dispatcher线程会受到巨大的性能影响。
如何以高效的方式向数据绑定的ObservableCollection中添加大量项
问题的原因:
问题是在一个MVVM架构的应用程序中,我有一个数据源,该数据源正在接收大量的更新,并且该数据源当前是与一个ListBox进行数据绑定的。考虑到ListBox上的自定义样式和其他一些与UI相关的因素,添加一百万个项需要相当长的时间,而在此期间UI似乎已经挂起。
解决方法:
如果添加项是耗时的操作,那么可以在另一个线程上执行。你提到的问题是NotifyCollectionChanged事件,这种方法可以消除这些事件。
具体代码如下:
private ListmyString = new List (); public IEnumerable MyString { get { return myString; } } private void click(object sender, RoutedEventArgs e) { for (int j = 1000; j < 1000000; j++) myString.Add(j.ToString()); lb.ItemsSource = null; lb.ItemsSource = MyString; }
另一个回答中提到了样式的问题,你没有实际测试过这个方法吗?
是的,虽然不是虚拟化出了问题,而是通过Dispatcher多次调用导致了这个问题。
通过在另一个线程上执行添加项的操作,可以避免UI挂起的问题。使用NotifyCollectionChanged事件来通知数据绑定的ObservableCollection有新的项添加进来。此外,还可以通过保存一组静态的行数据并逐个添加来提高性能。如果响应时间保持不变,则说明虚拟化功能正常工作;如果每次添加静态行时响应时间都增加,那么虚拟化功能可能出了问题。