WPF DataGrid能否直接绑定IAsyncEnumerable数据源?如何实现?
WPF中直接使用IAsyncEnumerable作为DataGrid数据源的实现方案
WPF的ItemsControl系列控件(包括DataGrid)原生仅支持同步枚举类型的数据源,无内置的IAsyncEnumerable适配逻辑,直接赋值作为ItemsSource不会触发数据加载和渲染。
以下方案可以完全保留IAsyncEnumerable流式加载的优势,无需等待全量数据返回即可逐步渲染UI:
以下方案可以完全保留IAsyncEnumerable流式加载的优势,无需等待全量数据返回即可逐步渲染UI:
核心实现思路
异步迭代IAsyncEnumerable返回的元素,每拿到一个/一批元素就封送到UI线程,添加到ObservableCollection中,利用ObservableCollection的集合变更通知触发DataGrid增量刷新。
实现代码
1. 基础版扩展方法(单条增量加载)
需要先安装NuGet包System.Linq.Async
using System.Collections.Generic; using System.Collections.ObjectModel; using System.Threading; using System.Threading.Tasks; using System.Windows.Threading; public static class AsyncEnumerableExtensions { /// <summary> /// 将IAsyncEnumerable转为增量加载的ObservableCollection /// </summary> /// <param name="asyncSource">异步数据源</param> /// <param name="uiDispatcher">UI线程Dispatcher</param> /// <param name="cancellationToken">取消令牌</param> public static async Task<ObservableCollection<T>> ToIncrementalObservableCollectionAsync<T>( this IAsyncEnumerable<T> asyncSource, Dispatcher uiDispatcher, CancellationToken cancellationToken = default) { var resultCollection = new ObservableCollection<T>(); await foreach (var item in asyncSource.WithCancellation(cancellationToken)) { // 封送回UI线程执行集合修改操作 uiDispatcher.Invoke( () => resultCollection.Add(item), DispatcherPriority.Background); } return resultCollection; } }
2. 优化版扩展方法(批量增量加载)
数据量较大时推荐使用,减少UI线程调度次数,降低渲染开销:
using System.Collections.Generic; using System.Collections.ObjectModel; using System.Threading; using System.Threading.Tasks; using System.Windows.Threading; public static class AsyncEnumerableExtensions { /// <summary> /// 将IAsyncEnumerable转为批量增量加载的ObservableCollection /// </summary> /// <param name="asyncSource">异步数据源</param> /// <param name="uiDispatcher">UI线程Dispatcher</param> /// <param name="batchSize">每批加载的元素数量</param> /// <param name="cancellationToken">取消令牌</param> public static async Task<ObservableCollection<T>> ToIncrementalObservableCollectionAsync<T>( this IAsyncEnumerable<T> asyncSource, Dispatcher uiDispatcher, int batchSize = 10, CancellationToken cancellationToken = default) { var resultCollection = new ObservableCollection<T>(); var tempBatch = new List<T>(batchSize); await foreach (var item in asyncSource.WithCancellation(cancellationToken)) { tempBatch.Add(item); if (tempBatch.Count >= batchSize) { uiDispatcher.Invoke(() => { foreach (var batchItem in tempBatch) { resultCollection.Add(batchItem); } }, DispatcherPriority.Background); tempBatch.Clear(); } } // 加载最后一批不足阈值的元素 if (tempBatch.Count > 0) { uiDispatcher.Invoke(() => { foreach (var batchItem in tempBatch) { resultCollection.Add(batchItem); } }, DispatcherPriority.Background); } return resultCollection; } }
用法示例
后台代码直接调用示例
// 窗口加载事件逻辑 private async void MainWindow_OnLoaded(object sender, RoutedEventArgs e) { // 先绑定空集合,提前渲染UI结构 var itemCollection = new ObservableCollection<YourDataModel>(); MyDataGrid.ItemsSource = itemCollection; // 异步拉取数据并增量加载,无需等待全量返回 await FetchDataFromDbAsync() // 该方法返回IAsyncEnumerable<YourDataModel> .ToIncrementalObservableCollectionAsync(Dispatcher); }
MVVM模式调用示例
在ViewModel中定义属性:
public ObservableCollection<YourDataModel> Items { get; set; } = new(); public async Task LoadDataAsync(CancellationToken cancellationToken) { await FetchDataFromDbAsync() .ToIncrementalObservableCollectionAsync( Application.Current.Dispatcher, cancellationToken: cancellationToken); }
XAML中绑定:
<DataGrid ItemsSource="{Binding Items}" x:Name="MyDataGrid"/>
注意事项
- 保持DataGrid的UI虚拟化功能开启(默认配置已开启),即使加载上万条数据也不会出现卡顿
- 可以结合加载状态标识,在枚举开始时显示加载中状态,枚举结束后隐藏,提升交互体验
- 无需额外实现IObservable接口,上述方案为轻量原生实现,完全保留IAsyncEnumerable的流式加载特性
内容的提问来源于stack exchange,提问作者Harry
相关产品推荐
相关产品推荐

