WPF数据表DataContext非常缓慢

7
我有一个简单的WPF应用程序正在进行中,它执行SQL查询并在DataGrid中显示结果数据。除了性能非常差之外,一切正常。从单击按钮到加载数据并实际看到数据出现在DataGrid中的时间大约为3-4秒。开启行虚拟化后速度会快得多,但我必须关闭它,因为需要对滚动后不再可见的单元格执行操作。即使开启了虚拟化,获取数据的显示速度也比我想象的要慢得多。
我最初认为是SQL数据库太慢了,但我进行了一些测试,发现我可以在几分之一秒内从SQL服务器将所有数据(几百行)读入DataTable中。只有当我将DataTable绑定到DataGrid的DataContext时,所有东西才会锁定几秒钟。
那么DataContext为什么这么慢呢?我的电脑是全新的,所以我很难理解考虑到我首先检索数据的速度,为什么填充DataGrid需要任何时间。
(我还尝试将其绑定到DataGrid的ItemSource而不是DataContext,但性能相同。)
是否有一种替代方法可以更合理地加载数据到DataGrid中?如果需要,甚至可以探索DataGrid的替代方案。
编辑:根据Vlad的建议,我尝试了另一个测试,绕过了SQL查询。我改为使用1000行随机生成的数据填充DataTable。没有任何变化。数据在不到一秒钟内生成并写入DataTable中。但将其附加到DataGrid上需要超过20秒。
以下是我正在使用的DataGrid XAML。除了绑定之外,它非常简单,在其上没有自定义代码。
<DataGrid ItemsSource="{Binding}" AutoGenerateColumns="False" Name="dataGridWellReadings" GridLinesVisibility="None" CanUserResizeRows="False" SelectionUnit="Cell" AlternatingRowBackground="#FFE0E0E0" RowBackground="#FFF0F0F0" HorizontalScrollBarVisibility="Disabled" SelectedCellsChanged="dataGridWellReadings_SelectedCellsChanged" EnableRowVirtualization="False">
    <DataGrid.Columns>
        <DataGridTextColumn Header="Date" Binding="{Binding readingDate, StringFormat=yyyy-MM-dd}" Width="3*">
            <DataGridTextColumn.CellStyle>
                <Style TargetType="DataGridCell">
                    <Setter Property="KeyboardNavigation.IsTabStop" Value="False" />
                    <Setter Property="BorderThickness" Value="0"/>
                </Style>
            </DataGridTextColumn.CellStyle>
        </DataGridTextColumn>
        <DataGridTextColumn Header="Pt" Binding="{Binding readingPt, StringFormat=0.#}" Width="2*">
            <DataGridTextColumn.CellStyle>
                <Style TargetType="DataGridCell">
                    <Setter Property="KeyboardNavigation.IsTabStop" Value="False" />
                    <Setter Property="BorderThickness" Value="0"/>
                </Style>
            </DataGridTextColumn.CellStyle>
        </DataGridTextColumn>
        <DataGridTextColumn Header="Pc" Binding="{Binding readingPc, StringFormat=0.#}" Width="2*">
            <DataGridTextColumn.CellStyle>
                <Style TargetType="DataGridCell">
                    <Setter Property="KeyboardNavigation.IsTabStop" Value="False" />
                    <Setter Property="BorderThickness" Value="0"/>
                </Style>
            </DataGridTextColumn.CellStyle>
        </DataGridTextColumn>
        <DataGridTextColumn Header="Ppl" Binding="{Binding readingPpl, StringFormat=0.#}" Width="2*">
            <DataGridTextColumn.CellStyle>
                <Style TargetType="DataGridCell">
                    <Setter Property="KeyboardNavigation.IsTabStop" Value="False" />
                    <Setter Property="BorderThickness" Value="0"/>
                </Style>
            </DataGridTextColumn.CellStyle>
        </DataGridTextColumn>
        <DataGridTextColumn Header="MCFD" Binding="{Binding readingMCFD, StringFormat=0.#}" Width="2*">
            <DataGridTextColumn.CellStyle>
                <Style TargetType="DataGridCell">
                    <Setter Property="KeyboardNavigation.IsTabStop" Value="False" />
                    <Setter Property="BorderThickness" Value="0"/>
                </Style>
            </DataGridTextColumn.CellStyle>
        </DataGridTextColumn>
        <DataGridTextColumn Header="Water Produced" Binding="{Binding readingWaterProduced, StringFormat=0.#}" Width="3*">
            <DataGridTextColumn.CellStyle>
                <Style TargetType="DataGridCell">
                    <Setter Property="KeyboardNavigation.IsTabStop" Value="False" />
                    <Setter Property="BorderThickness" Value="0"/>
                </Style>
            </DataGridTextColumn.CellStyle>
        </DataGridTextColumn>
        <DataGridTextColumn Header="Water Hauled" Binding="{Binding readingWaterHauled, StringFormat=0.#}" Width="3*">
            <DataGridTextColumn.CellStyle>
                <Style TargetType="DataGridCell">
                    <Setter Property="KeyboardNavigation.IsTabStop" Value="False" />
                    <Setter Property="BorderThickness" Value="0"/>
                </Style>
            </DataGridTextColumn.CellStyle>
        </DataGridTextColumn>
        <DataGridTextColumn Header="Temperature" Binding="{Binding readingTemperature, StringFormat=0.#}" Width="3*">
            <DataGridTextColumn.CellStyle>
                <Style TargetType="DataGridCell">
                    <Setter Property="KeyboardNavigation.IsTabStop" Value="False" />
                    <Setter Property="BorderThickness" Value="0"/>
                </Style>
            </DataGridTextColumn.CellStyle>
        </DataGridTextColumn>
        <DataGridTextColumn Header="Hours On (actual)" Binding="{Binding readingHoursOnActual, StringFormat=0.#}" Width="3*">
            <DataGridTextColumn.CellStyle>
                <Style TargetType="DataGridCell">
                    <Setter Property="KeyboardNavigation.IsTabStop" Value="False" />
                    <Setter Property="BorderThickness" Value="0"/>
                </Style>
            </DataGridTextColumn.CellStyle>
        </DataGridTextColumn>
        <DataGridTextColumn Header="Hours On (planned)" Binding="{Binding readingHoursOnPlanned, StringFormat=0.#}" Width="3*">
            <DataGridTextColumn.CellStyle>
                <Style TargetType="DataGridCell">
                    <Setter Property="KeyboardNavigation.IsTabStop" Value="False" />
                    <Setter Property="BorderThickness" Value="0"/>
                </Style>
            </DataGridTextColumn.CellStyle>
        </DataGridTextColumn>
        <DataGridTextColumn Header="Clock Cycles" Binding="{Binding readingClockCycles, StringFormat=0.#}" Width="3*">
            <DataGridTextColumn.CellStyle>
                <Style TargetType="DataGridCell">
                    <Setter Property="KeyboardNavigation.IsTabStop" Value="False" />
                    <Setter Property="BorderThickness" Value="0"/>
                </Style>
            </DataGridTextColumn.CellStyle>
        </DataGridTextColumn>
    </DataGrid.Columns>
</DataGrid>

1
你确定是 DataContext 慢吗?可能是因为 LINQ 没有实现查询,当 UI 绑定到它时才惰性执行。请检查是否在非UI线程中实现了查询。 - Vlad
1
或者只是尝试使用一些虚假数据,而不需要访问数据库来执行相同的代码。 - Vlad
好的,我再次进行了测试,这次没有使用数据库,只是使用了1000行随机生成的数据。同样地,数据在不到一秒钟的时间内被生成并写入到了一个 DataTable 中。然而,将其附加到 DataGrid 上却花费了超过20秒的时间。 - Nairou
6个回答

18

由于存在太多的变量,无法确定性地回答这个问题。不过,以下是一些需要考虑的事项:

  1. 您是否提供了比用户实际使用更多的数据,导致输入框中的数据过多?这可能会使程序变慢。

  2. 您是否使用了太多的模板来呈现列或单元格?这可以使您的界面灵活,但使用太多的模板(或控件)会使程序变慢。

  3. 您的datagrid中是否有很多值转换器?每行或每列都必须运行一些耗费时间的代码才能呈现吗?

  4. 您是否在样式中使用了许多基于BasedOn的嵌套样式,以及许多触发器从而占用了渲染时间来应用这些样式?

  5. 您的界面中是否使用了许多用户控件,尤其是嵌套的控件,从而导致渲染延迟?

  6. 您是否在单元格的绑定字符串中使用了复杂的StringFormat、ElementName或Ancestory查找?这也会导致渲染速度变慢。

  7. 您是否使用了视觉画刷来显示比用户立即看到的更多数据?这可能会破坏虚拟化逻辑。

  8. 您是否考虑过为绑定设置FallBackValue并将IsAsync设置为true?将其设置为true将显示FallBackValue,直到数据准备好。

  9. 您的界面中是否使用了许多MultiBindings或PriorityBindings,从而导致渲染速度变慢,因为它处理了多个字段或值?

  10. 您使用的样式是否复杂?特别是渐变画刷,每一行都进行渲染可能会很耗费时间。

  11. 您是否考虑过使用分页来减少数据?实际上,如果用户可以接受,这是这类问题的最佳解决方案。

  12. 您是否关注内存和CPU使用情况?您使用的硬件是否在努力呈现您创建的UI?

  13. 您是否查看调试输出以查看是否存在绑定错误或其他影响性能下降的被忽略的错误?

  14. 您是否对屏幕微笑,并使您的代码感到舒适,从而愿意为您更加努力?开个玩笑,但事实上有太多的变量——是吧?

  15. 您是否实现了命令,其CanExecute处理程序会非常频繁地被调用,并且执行起来可能很耗费时间?这些可能会默默地占用性能。

总之,没有100%的答案来解决这个问题。但这些建议或许可以帮助您。

还有一件需要考虑的事情是,您的枚举可以是一个可观察的列表 - 这将让您分批加载数据。如果您想异步地加载第一页并在其中附加下一页、下一页等,那么用户体验应该与一次性加载数据非常接近,只不过初始渲染速度更快。这可能比较复杂,但这是另一个选择。可观察的列表就是这

ObservableCollection<User> Users { get; set; }

void LoadUsers()
{
    int _Size = 2;
    int _Page = 0;
    using (System.ComponentModel.BackgroundWorker _Worker
        = new System.ComponentModel.BackgroundWorker())
    {
        _Worker.WorkerReportsProgress = true;
        _Worker.DoWork += (s, arg) =>
        {
            List<User> _Data = null;
            while (_Data == null || _Data.Any())
            {
                _Data = GetData(_Size, _Page++);
                _Worker.ReportProgress(_Page, _Data);
            }
        };
        _Worker.ProgressChanged += (s, e) =>
        {
            List<User> _Data = null;
            _Data = e.UserState as List<User>;
            _Data.ForEach(x => Users.Add(x));
        };
        _Worker.RunWorkerAsync();
    }
}

List<User> GetData(int size, int page)
{
    // never return null
    return m_Context.Users.Take(size).Skip(page).ToList();
}

这是我希望您了解的 - 在WPF中,绑定永远不会是瞬时的。 您将永远无法在没有任何延迟的情况下呈现和绑定复杂的表单。 您可以使用上述一些技术来控制这种痛苦。 然而,你永远无法完全消除它。 但是,在WPF中进行绑定是我曾经体验过的最强大和令人惊叹的绑定技术。

祝好运!


我已更新我的帖子,展示了我正在使用的DataGrid XAML。它非常简单,没有自定义代码,这也是我不理解它为什么会变慢的原因之一。 - Nairou
异步加载可观察列表听起来很有前途。你有关于如何实现它的链接吗? - Nairou

2
您可能会发现,慢的表现与附加本身无关,而是在显示数据时发生的DataGrid重绘造成的。您提到的延迟似乎相当过分。
我曾经遇到一个DataGrid问题,在窗口调整大小、列排序等操作后,刷新需要几秒钟时间,并且在此期间锁定了窗口UI(1000行,5列)。
问题出在WPF大小计算上(是否为bug?)。我将其放在RowDefinition Height="Auto"的网格中,导致渲染系统在运行时尝试通过测量每个列和行的大小来重新计算DataGrid的大小,可能通过填充整个网格来实现(据我所知)。它应该以某种智能方式处理这个问题,但在这种情况下却没有。 快速检查是否存在相关问题是将DataGrid的Height和Width属性设置为固定大小,然后再次运行测试。如果性能得到恢复,则永久解决方案可能包括以下选项:
  • 更改包含元素的大小为相对(*)或固定值
  • 将DataGrid的MaxHeight和MaxWidth设置为固定值,大于正常使用时可能获得的值
  • 尝试另一种具有不同调整大小策略的容器类型(Grid、DockPanel等)。事实上,我找到的最简单的解决方案是将DataGrid放在一个网格中作为其直接容器,DataGrid是唯一的元素。

1

这可能对一些人有用:

我有一个数据网格,绑定速度很慢,就像原帖作者一样。

应用程序在短时间内查询数据库并执行了许多逻辑,然后花费了一秒或更长时间来将可观察集合简单地绑定到数据网格。 在我的情况下,尽管大部分数据已经准备好了,但其中一些是惰性加载的(意思是直到需要时才加载 - 这是大多数ORM工具(如NHibernate、iBatis等)的常见且有用的部分)。 直到绑定发生之前,它都不是必需的。 在我的情况下,不是所有数据,而只有一个单独的列是惰性加载的。 结果证明,WPF已经有了一个非常简单的机制来处理这样的问题。 将此列的绑定设置为以下内容即可解决问题:

<Binding Path="SomeProperty" IsAsync="True" FallbackValue="..." />

我的数据网格几乎瞬间加载完毕。其中一列仅包含文本“...”几秒钟,然后正确的数据出现了。

0

我对WPF还很陌生,但我发现如果在同一个数据网格上的多个位置分配EnableRowVirtualization,当渲染网格时,它几乎会使您的应用程序瘫痪。我们正在开发的应用程序中,我已经发现了两次这种情况。一次是在样式中设置了2次,另一次是在行为和样式中都设置了。我建议检查一下是否在同一个数据网格上进行了多次虚拟化。


0

帖子已更新,附上代码。我会看一下那个分析器。不过,我的DataGrid几乎没有自定义的后台代码,所以我不确定它对我有多大帮助。 - Nairou

0

您是否在应用程序的Debug或Release版本中遇到了这种缓慢的情况?是否附加了Visual Studio?

如果是在带有Visual Studio的Debug版本中,则可能是DataBinding错误被写入了输出窗口。我之所以这样说,是因为今晚早些时候我解决了一个ListBox中的显著暂停/缓慢问题,该ListBox显示了5000多个项目,这是由于 ListBoxItem 的默认模板尝试执行 VerticalContentAlignment HorizontalContentAlignment 的绑定,而这些绑定总是失败。


无论是在 Visual Studio 中还是不在其中,无论是 Debug 还是 Release,速度似乎都是一样的。我查看了输出窗口,但没有看到任何异常项。 - Nairou
那么,我认为你的解决方案将是重新打开虚拟化,并重新考虑你的模型,以便在单元格被虚拟化后与它们进行交互。 - Dennis
我可能不得不这样做。我只希望我能理解性能损失是由于什么引起的。所有数据都在内存中。我不明白为什么DataGrid在显示它时如此缓慢。 - Nairou
它只是为您显示的每一行创建大量复杂的UI元素。打开虚拟化后,至少这个数字显着减少了。如果您真正想了解,请获取Snoop(它是一个WPF内省工具),并在应用程序上打开放大工具。 - Dennis
补充你的观点,任何UI元素都是复杂的UI元素。一些用户会把文本框误认为是简单的,或者把按钮误认为是简单的。事实上,每个元素都有代价,没有哪个元素是“本地”的或“闪电般”快速的。话虽如此,它们仍然很快 - 只是从来不是瞬间的。 - Jerry Nixon
谢谢额外指点,Jerry - 一切都是有代价的。我强烈推荐使用像Blend或者WPF Mole这样的内省工具来深入挖掘可视化树,以便了解您实际渲染的内容。 - Dennis

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接