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)的字段不一致,会产生多个绑定错误:

image

大量的绑定错误会导致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时因为数据源已经被置空,所以不会产生绑定错误,界面不会卡顿。

posted @ 2026-02-02 15:17  .NetDomainer  阅读(14)  评论(0)    收藏  举报