如何以高效的方式将大量项目添加到数据绑定的ObservableCollection中

11 浏览
0 Comments

如何以高效的方式将大量项目添加到数据绑定的ObservableCollection中

之前的几个问题中提到了相关问题(见下文),但讨论的解决方案似乎都不适用于这种情况。此应用程序使用 ObservableCollection 通过数据绑定来驱动一个常规的 WPF ListBox(使用一些自定义样式)。ObservableCollection 可能包含大量项,但由于 ListBox 默认以虚拟方式运行,这不是问题。(拥有数十万项的“静态”性能非常可观。)

然而,如果集合已经包含大量项,然后通过 ObservableCollection.Add() 添加了更多项,不可避免地由于所有的 NotifyCollectionChanged 事件被触发,UI 将暂时停止响应。在这种情况下,推荐的解决方法是对 ObservableCollection 进行子类化,并实现一个 AddRange() 方法,该方法将新元素添加到(受保护的)Items 集合中,然后使用 NotifyCollectionChangedAction.Reset 调用 NotifyCollectionChanged

根据我的经验,这解决了一个问题,但又引发了另一个问题。虽然不再引发大量的 Add 事件,但 Reset 通知会导致 ListBox 重新评估整个集合,如果集合包含数以万计的项,这可能需要相当长的时间。再加上目标应用程序中可能经常添加大量项,这意味着应用程序会在 ListBox 消化所有新信息的过程中长时间占用大量 CPU。

可以争论的是,事后看来 ObservableCollection 不是向用户提供如此大数据集的正确方式,但是既然已经走到这一步,很难看到一个简单的重新架构应用程序来解决此问题的方法。

非常感谢您提供的建议。

0
0 Comments

问题的出现原因:

直接将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卡顿或冻结的问题。这种方法还可以在后台线程上获取记录,并且允许对视图进行多种操作。

0
0 Comments

问题的原因是在添加大量项到数据绑定的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线程会受到巨大的性能影响。

0
0 Comments

如何以高效的方式向数据绑定的ObservableCollection中添加大量项

问题的原因:

问题是在一个MVVM架构的应用程序中,我有一个数据源,该数据源正在接收大量的更新,并且该数据源当前是与一个ListBox进行数据绑定的。考虑到ListBox上的自定义样式和其他一些与UI相关的因素,添加一百万个项需要相当长的时间,而在此期间UI似乎已经挂起。

解决方法:

如果添加项是耗时的操作,那么可以在另一个线程上执行。你提到的问题是NotifyCollectionChanged事件,这种方法可以消除这些事件。

具体代码如下:

private List myString = 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有新的项添加进来。此外,还可以通过保存一组静态的行数据并逐个添加来提高性能。如果响应时间保持不变,则说明虚拟化功能正常工作;如果每次添加静态行时响应时间都增加,那么虚拟化功能可能出了问题。

0