65.9K
CodeProject 正在变化。 阅读更多。
Home

克服 ObservableCollection 中不断重新排序的数据对 UI 线程造成的压力

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.72/5 (7投票s)

2014年10月20日

CPOL

4分钟阅读

viewsIcon

17757

downloadIcon

224

提供了一种替代 ObservableCollection 的方案,对 UI 密集度较低

引言

如果您使用过WPF,那么您无疑遇到过一个问题,即为了更新ObservableCollection,您必须在UI线程上花费很长时间。通常对于少量更新,这并不是一个问题,但当处理经常更新且顺序不断变化的数据时(例如来自经纪商列表的最佳价格),这可能会成为一个问题。本文探讨了一种解决此问题的方法。

背景 

在我的职业生涯中,我曾几次遇到过这个问题,并且在面临特别高容量的事件时想到了这个解决方案。

通常,在我所处的大多数环境中,数据都是通过某种形式的服务远程获取的,并且在所有情况下,通过后台工作线程(任务池等)处理数据是常识。但是,在某些时候,需要显示数据,当它涉及到不仅仅是属性更新时,我们通常需要在调度程序线程上调用BeginInvokeInvoke。如果您这样做过,您通常会知道不断调用调度程序如何迅速导致UI无响应。

那么,什么时候我们需要调用BeginInvokeInvoke? 只有当集合需要更改时,例如添加、删除或更改其顺序。那么,我们如何防止这种情况发生?答案是对于需要添加或删除的项,我们无法做到,但对于重新排序的数据,存在另一种方法。

在典型的ObservableCollection中,您可能会决定通过CollectionViewSource或通过交换需要更改位置的项目(作为确定排序索引的值更改)来处理重新排序。这两种方法都需要在UI线程上执行,如果频繁调用,将会降低UI的响应速度。我所采用的方法是,与其像传统的OO方法那样查看类的属性(仅属于一个类),不如将它们视为可互换的。从实践角度来看,这意味着,不是对象更改位置,而只是交换它们的数据。这样做最明显的优势是,可以在工作线程上进行属性更改,因此对UI性能没有影响。

这是通过包装一个ObservableCollection,并且仅在集合增长或收缩时通过添加或删除项目来更新它来实现的。在所有其他时间,项目的内容被交换,允许在后台线程上执行处理,从而释放UI线程以更新UI。

虽然这种方法对于特定大小的集合效果很好,但随着集合变得越来越大,性能会迅速下降,因为排序和插入通常需要大量迭代。

Using the Code

zip文件分为三个项目:

ReorderableObservableCollection ReorderableObservableCollection 源代码

ReorderableObservableCollection.Tests 一个小的单元测试项目

ReorderedCollectionExample 一个关于性能差异以及如何使用集合的示例。

ReorderableObservableCollection

在大多数情况下,该集合可以像任何其他集合一样处理,但值得记住一件事。您不能依赖ReferencesEquals来确定集合中包含的对象是否相同,因为内容将被交换,因此您需要实现IEquatable<>

区别

为了实现性能,可观察集合由类包装,并作为IEnumerable集合公开。它的设计目的是允许批量进行更改,然后同步更改,这些更改将在UI线程上执行。

要将集合绑定到控件,您需要引用ObservableCollection属性。

//
// This property exposes the wrapped observable collection and should be bound
// to the control used to display the collection in your view
//
public IEnumerable<T> ObservableCollection
 
Example
<DataGrid ItemsSource="{Binding Path=ReorderableData.ObservableCollection}" />

同步底层集合。

//
// Updates the observable collection with any changes made
//
public void Sync()


//
// Updates the observable collection from the maintained list and resets the changes.
// If a large number of changes have been made then this can be more efficient.
//
public void SyncReset()

将项目插入到正确的位置以保持排序顺序。

//
// Inserts an item into the correct location in the collection, based on its IComparable<>
// implementation
//
public void InsertInOrder(T item)
 
Example
ReorderableData.InsertInOrder(new ExampleClass());


//
// Inserts an item into the correct location in the collection, based on its 
// IComparer<T> implementation
//
public void InsertInOrder(IComparer<T> comparer, T item)
 
Example
public class SortOrder : IComparer<ExampleClass>
{
  public int Compare(ExampleClass x, ExampleClass y)
  {
     return x.Index.CompareTo(y.Index);
  }
}

ReorderableData.InsertInOrder(this, new ExampleClass());

将集合排序为有序状态。

//
// Performs a quick sort on the collection using its IComparable<> implementation
//
public void Sort()
 
Example
ReorderableData.Sort();


//
// Performs a quick sort on the collection, using a supplied on its IComparer<T> implementation
//
public void Sort(IComparer<T> comparer)
 
Example
public class SortOrder : IComparer<ExampleClass>
{
  public int Compare(ExampleClass x, ExampleClass y)
  {
     return x.Index.CompareTo(y.Index);
  }
}

ReorderableData.Sort( new SortOrder());

演示程序

演示程序旨在显示在对ObservableCollection进行排序时放置在UI线程上的负载与不触及UI线程的ReorderableObservableCollection之间的差异。

Example

顶部数据网格绑定到ReorderableObservableCollection,较低的网格绑定到ObservableCollection。您会注意到一个睡眠值,这是给予更新ObservableCollection的线程的睡眠时间。减少此睡眠时间,您会很快发现UI变得无响应,增加睡眠时间,您会发现更新次数减少。这可以优化,以便调用BeginInvoke受到限制,但在商业应用程序中,会发生其他UI活动,而演示程序并未模拟这些活动。

© . All rights reserved.