WPF中异步代码更新DataGrid控件卡顿问题分析
问题背景
在 WPF 开发中,我们经常需要动态更改 DataGrid 的列结构和数据源。例如,在报表系统中切换不同的数据视图,或在数据分析工具中展示不同结构的数据集。然而,这个看似简单的操作却隐藏着一个容易被忽视的陷阱。
问题复现
假设我们有以下场景:两个 DataTable,列名完全不同,需要在它们之间切换显示。
初始代码
xaml代码如下:
<Window x:Class="WpfApp1.DataGridFreezeDemoWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp1" mc:Ignorable="d" Title="DataGridFreezeDemoWindow" Height="450" Width="800"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="*"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <DataGrid x:Name="dg"/> <Button x:Name="btnUpdate" Grid.Row="1" Click="UpdateButton_Click" >Update Columns And Rows</Button> </Grid> </Window>
xaml.cs代码如下:
public DataGridFreezeDemoWindow() { InitializeComponent(); _dt = new DataTable(); // 向_dt中添加50列;"Column"开头 for (int i = 0; i < 50; i++) { _dt.Columns.Add($"Column{i}", typeof(string)); } // 添加500行随机数据到_dt Random random = new Random(); for (int i = 0; i < 500; i++) { DataRow row = _dt.NewRow(); for (int j = 0; j < 50; j++) { row[j] = $"Row{i}_Col{j}_{random.Next(1000)}"; } _dt.Rows.Add(row); } _dt.AcceptChanges(); // 使用不同的列名初始化_dt2,"Field"开头 _dt2 = new DataTable(); for (int i = 0; i < 50; i++) { _dt2.Columns.Add($"Field_{i + 1}", typeof(string)); } // 向_dt2添加500行随机数据 for (int i = 0; i < 500; i++) { DataRow row = _dt2.NewRow(); for (int j = 0; j < 50; j++) { row[j] = $"Data{i}_F{j + 1}_{random.Next(5000, 10000)}"; } _dt2.Rows.Add(row); } _dt2.AcceptChanges(); // 设定是否使用的第一个datatable _useFirstDataTable = false; ApplyDataGridColumns(_dt); dg.ItemsSource = _dt.DefaultView; } private DataTable _dt; private DataTable _dt2; private bool _useFirstDataTable = true;
private void ApplyDataGridColumns(DataTable dataTable) { // Disable auto-generated columns dg.AutoGenerateColumns = false; // Clear existing columns dg.Columns.Clear(); // Manually create columns for the DataTable for (int i = 0; i < dataTable.Columns.Count; i++) { DataGridTextColumn column = new DataGridTextColumn { Header = dataTable.Columns[i].ColumnName, Binding = new Binding($"[{i}]"), IsReadOnly = true }; dg.Columns.Add(column); } }
初看之下,切换逻辑很简单
private async void UpdateButton_Click(object sender, RoutedEventArgs e) {
btnUpdate.IsEnabled = false;
try { DataTable currentDataTable = _useFirstDataTable ? _dt : _dt2; // 更新列定义 ApplyDataGridColumns(currentDataTable); //模拟实际项目中的一些耗时的异步操作 await Task.Delay(500); // 绑定新数据 dg.ItemsSource = currentDataTable.DefaultView; _useFirstDataTable = !_useFirstDataTable; } finally { btnUpdate.IsEnabled = true; }
}
问题所在
这段代码存在一个严重的隐藏问题:在 `await Task.Delay(500)` 期间,WPF 会接管 UI 线程并尝试更新视图,在WPF更新UI时,由于当前DataGrid的列和其绑定数据源(DataTable)的字段不一致,会产生多个绑定错误:

大量的绑定错误会导致UI卡顿甚至卡死。
为了解决这个问题,我们可以在应用新的列数据之前,将DataGrid的数据源设置为null:
private async void UpdateButton_Click(object sender, RoutedEventArgs e) { // Disable the button during execution btnUpdate.IsEnabled = false; try { // Toggle between the two data tables DataTable currentDataTable = _useFirstDataTable ? _dt : _dt2; // Apply columns only ApplyDataGridColumns(currentDataTable); // Wait for WPF to update the view with the new columns await Task.Delay(500); // Bind the data after delay dg.ItemsSource = currentDataTable.DefaultView; // Toggle for next click _useFirstDataTable = !_useFirstDataTable; } finally { // Re-enable the button after execution completes btnUpdate.IsEnabled = true; } }
此时,执行ApplyDataGridColumns(currentDataTable)后,WPF更新UI时因为数据源已经被置空,所以不会产生绑定错误,界面不会卡顿。

浙公网安备 33010602011771号