我们在窗口上有一个绑定到对象集合的数据网格。我们在这个窗口上的表现很糟糕;加载最多可能需要20秒,然后每次“滚动”数据网格需要5-7秒。到目前为止共有12个项目。当我们进行调查时,似乎放缓的主要原因是财产吸收者;我们的一些吸气剂被称为超过20,000次(每个物体的1666.667次)!仪器显示我们的吸气剂不是特别慢;最慢的一个花了0.002秒。不幸的是,当你乘以0.0002 * 20k时,你很容易想出我们遇到的延迟。
我们坐下来创建了一个问题证明示例项目。在这个项目中,我们创建了一个简单的死类,一个简单的视图模型和一个非常基本的窗口。我们添加了一些代码来尝试观察问题。虽然我们没有看到任何接近我们之前看到的问题的大小,但这是一个更简单的屏幕;对我们来说更令人不安的是,我们确实看到了我们认为对财产吸收者“过度”使用的证据。在此示例中,当您运行屏幕时,它们从1开始;当你向下滚动然后再向上滚动时,其中一些现在最多可达5或6;向后滚动,你会看到一些底部的最多9个左右。
在这个概念证明的情况下,这不是问题;但是我们的实际对象要复杂得多,我们无法承受40秒的延迟访问房产20,000次!有谁知道这里发生了什么?如何让网格不那么激进地轮询我的对象?我们使用的是WPF-Toolkit DataGrid和.NET 3.5版。 PoC的示例代码如下:
***** WINDOW CODE *****
<Window x:Class="PocApp.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:WpfToolkit="clr-namespace:Microsoft.Windows.Controls;assembly=WPFToolkit"
Title="Window1" Height="200" Width="200">
<Grid>
<WpfToolkit:DataGrid ItemsSource="{Binding Path=MyEntries}">
</WpfToolkit:DataGrid>
</Grid>
</Window>
***** WINDOW CODE-BEHIND *****
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace PocApp
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
this.DataContext = new EntryViewModel();
}
}
}
***** ENTRY CLASS *****
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace PocApp
{
public class Entry
{
public Entry(string first, string last)
{
FirstName = first;
LastName = last;
}
public string FirstName
{
get
{
firstCall++;
System.Console.WriteLine("FirstName Call:" + firstCall);
return _firstname;
}
set
{
_firstname = value;
}
}
public int FirstNameCallCount
{
get
{
return firstCall;
}
}
public string LastName
{
get
{
lastCall++;
System.Console.WriteLine("LastName Call:" + lastCall);
return _lastname;
}
set
{
_lastname = value;
}
}
public int LastNameCallCount
{
get
{
return lastCall;
}
}
private string _firstname,_lastname;
private int firstCall,lastCall = 0;
}
}
***** ENTRY VIEW MODEL CLASS *****
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Data;
namespace PocApp
{
public class EntryViewModel
{
public EntryViewModel()
{
List<Entry> myCoolEntries = new List<Entry>()
{
new Entry("A","A1"),
new Entry("B","B1"),
new Entry("C","C1"),
new Entry("D","D1"),
new Entry("E","E1"),
new Entry("F","F1"),
new Entry("G","G1"),
new Entry("H","H1"),
new Entry("I","I1"),
new Entry("J","J1"),
new Entry("K","K1"),
new Entry("L","L1"),
new Entry("M","M1"),
new Entry("N","N1"),
new Entry("O","O1"),
new Entry("P","P1"),
new Entry("Q","Q1"),
};
MyEntries = (CollectionView)CollectionViewSource.GetDefaultView(myCoolEntries);
}
public CollectionView MyEntries
{
get;
private set;
}
}
}
答案 0 :(得分:2)
您在概念验证中看到的数字实际上是正常的。它们是数据网格内部行虚拟化的结果;当一个项目滚动到视图外时,容器行被重新用于显示新输入的项目,从而相对于创建和操作的实际可视控件保持较低的内存使用量。显示另一个项目意味着重新查找绑定的属性。
通常我不认为这会是一个问题;我使用了非常复杂的业务对象,并在数据网格中显示了大量数据,而没有提到你遇到的那些问题。我建议看看应用于数据网格的任何样式,因为它们可能会干扰行的虚拟化;另外,执行时间为2毫秒的属性获取器并不是我所说的快速:)
编辑:实际上,只有通过禁用虚拟化才能避免对属性getter的额外调用,虚拟化会产生巨大的性能问题 - 大量浪费的内存和极其滚动的性能甚至是'数据网格中的少量(数千)行。然而,这些是我在没有更多信息的情况下可以说的唯一内容。如果可能的话,尝试在调用getter时逐步执行代码,并更准确地确定大多数调用的来源和时间。如果在滚动时出现问题,那么您的实体确实非常复杂,我怀疑以这种方式在数据网格中显示它们的价值。
另一个编辑:我更仔细地重新阅读了这个问题,我注意到你说 12 项目?!我很抱歉,但现在我非常坚定地说数据网格不应该因此而受到指责,除非您的对象有数千个属性,所有属性都绑定到数据网格中的列,我怀疑这是完全可能的。请检查其余的代码和样式,并尝试确定我们可以建议您的任何潜在问题区域......不确定还有什么要说的。还要检查实体是否不会不必要地引发NotifyPropertyChanged事件,因为这会导致绑定控件重新查询属性。