WPF MVVM:如何异步加载含子ViewModel的复杂ViewModel数据
解决方案:分层异步加载+状态聚合+缓存复用
针对复杂嵌套ViewModel的异步加载场景,结合你的需求,推荐采用分层异步职责划分+全局状态聚合+缓存层复用的方案,同时规范ObservableCollection的初始化方式,以下是具体实现细节:
一、核心设计思路
- 每个ViewModel独立管理自身的异步属性加载逻辑,避免单一Load方法臃肿
- 通过父ViewModel聚合所有子ViewModel的加载状态,提供统一的忙碌/错误状态
- 引入线程安全的缓存层,彻底避免重复数据库请求
- 基于IoC容器实现依赖注入与子ViewModel的动态创建,符合依赖倒置原则
二、具体实现步骤
1. 实现线程安全的数据缓存层
创建全局缓存服务,注册到IoC容器,所有ViewModel通过该服务获取数据,自动避免重复请求:
public interface IDataCache { Task<T> GetOrAddAsync<T>(string key, Func<Task<T>> factory, TimeSpan? expiration = null); } public class MemoryDataCache : IDataCache { private readonly IMemoryCache _cache; private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); public MemoryDataCache(IMemoryCache cache) { _cache = cache; } public async Task<T> GetOrAddAsync<T>(string key, Func<Task<T>> factory, TimeSpan? expiration = null) { if (_cache.TryGetValue(key, out T value)) return value; await _semaphore.WaitAsync(); try { if (_cache.TryGetValue(key, out value)) return value; value = await factory().ConfigureAwait(false); var options = new MemoryCacheEntryOptions(); if (expiration.HasValue) options.SetAbsoluteExpiration(expiration.Value); _cache.Set(key, value, options); return value; } finally { _semaphore.Release(); } } }
IoC容器注册(以ASP.NET Core为例):
services.AddMemoryCache(); services.AddSingleton<IDataCache, MemoryDataCache>();
2. 基于NotifyTask的ViewModel异步属性设计
每个ViewModel维护自身的NotifyTask属性,独立处理加载逻辑,并暴露自身的状态:
public class ChildViewModel : INotifyPropertyChanged { private readonly IDataCache _cache; private NotifyTask<Entity> _entityTask; private NotifyTask<List<SubEntity>> _subEntitiesTask; public NotifyTask<Entity> EntityTask { get => _entityTask; private set => SetProperty(ref _entityTask, value); } public NotifyTask<List<SubEntity>> SubEntitiesTask { get => _subEntitiesTask; private set => SetProperty(ref _subEntitiesTask, value); } public bool IsBusy => EntityTask.IsNotCompleted || SubEntitiesTask.IsNotCompleted; public bool HasError => EntityTask.IsFaulted || SubEntitiesTask.IsFaulted; public ChildViewModel(IDataCache cache) { _cache = cache; } public async Task LoadAsync(int entityId) { EntityTask = NotifyTask.Create(LoadEntityAsync(entityId)); SubEntitiesTask = NotifyTask.Create(LoadSubEntitiesAsync(entityId)); await Task.WhenAll(EntityTask.Task, SubEntitiesTask.Task).ConfigureAwait(false); } private async Task<Entity> LoadEntityAsync(int entityId) { return await _cache.GetOrAddAsync($"Entity_{entityId}", () => _dataService.GetEntityAsync(entityId), TimeSpan.FromMinutes(5)).ConfigureAwait(false); } private async Task<List<SubEntity>> LoadSubEntitiesAsync(int entityId) { return await _cache.GetOrAddAsync($"SubEntities_{entityId}", () => _dataService.GetSubEntitiesAsync(entityId), TimeSpan.FromMinutes(5)).ConfigureAwait(false); } // INotifyPropertyChanged实现 public event PropertyChangedEventHandler PropertyChanged; protected void SetProperty<T>(ref T field, T value, [CallerMemberName] string propertyName = null) { if (!EqualityComparer<T>.Default.Equals(field, value)) { field = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } }
3. 父ViewModel的状态聚合与子ViewModel管理
父ViewModel负责创建子ViewModel、触发加载,并通过监听子ViewModel的状态变化,聚合全局状态:
public class ParentViewModel : INotifyPropertyChanged { private readonly IDataCache _cache; private readonly IServiceProvider _serviceProvider; private NotifyTask<List<ChildViewModel>> _childViewModelsTask; private bool _isBusy; private bool _hasError; public NotifyTask<List<ChildViewModel>> ChildViewModelsTask { get => _childViewModelsTask; private set => SetProperty(ref _childViewModelsTask, value); } public bool IsBusy { get => _isBusy; private set => SetProperty(ref _isBusy, value); } public bool HasError { get => _hasError; private set => SetProperty(ref _hasError, value); } public ParentViewModel(IDataCache cache, IServiceProvider serviceProvider) { _cache = cache; _serviceProvider = serviceProvider; } public async Task LoadAsync() { ChildViewModelsTask = NotifyTask.Create(LoadChildViewModelsAsync()); await ChildViewModelsTask.Task.ConfigureAwait(false); UpdateAggregateState(); // 监听子ViewModel状态变化,实时更新全局状态 foreach (var childVm in ChildViewModelsTask.Result) { childVm.PropertyChanged += (s, e) => { if (e.PropertyName is nameof(ChildViewModel.IsBusy) or nameof(ChildViewModel.HasError)) { UpdateAggregateState(); } }; } } private async Task<List<ChildViewModel>> LoadChildViewModelsAsync() { var parentData = await _cache.GetOrAddAsync("ParentData", () => _dataService.GetParentDataAsync(), TimeSpan.FromMinutes(10)).ConfigureAwait(false); var childVms = new List<ChildViewModel>(); foreach (var entityId in parentData.ChildEntityIds) { var childVm = _serviceProvider.GetRequiredService<ChildViewModel>(); _ = childVm.LoadAsync(entityId); // 异步加载,不阻塞父VM childVms.Add(childVm); } return childVms; } private void UpdateAggregateState() { if (ChildViewModelsTask?.Result == null) { IsBusy = true; HasError = false; return; } IsBusy = ChildViewModelsTask.Result.Any(vm => vm.IsBusy); HasError = ChildViewModelsTask.Result.Any(vm => vm.HasError); } // INotifyPropertyChanged实现略 }
4. ObservableCollection的正确初始化方式
禁止在后台线程创建ObservableCollection,因为它绑定到UI线程的调度上下文。正确做法是:在后台线程获取普通List数据,再切换到UI线程创建ObservableCollection:
// 方式1:通过Dispatcher切换到UI线程 private async Task<ObservableCollection<Item>> LoadItemsAsync() { // 后台线程获取原始数据 var rawItems = await _cache.GetOrAddAsync("Items", () => _dataService.GetItemsAsync(), TimeSpan.FromMinutes(5)).ConfigureAwait(false); // 切换到UI线程创建ObservableCollection return await Application.Current.Dispatcher.InvokeAsync(() => new ObservableCollection<Item>(rawItems)); } // 方式2:利用NotifyTask的完成回调 public ParentViewModel(IDataCache cache) { _rawItemsTask = NotifyTask.Create(LoadRawItemsAsync(), onCompleted: () => { // 完成后在UI线程创建集合 Items = new ObservableCollection<Item>(_rawItemsTask.Result); OnPropertyChanged(nameof(Items)); }); } private async Task<List<Item>> LoadRawItemsAsync() { return await _cache.GetOrAddAsync("Items", () => _dataService.GetItemsAsync(), TimeSpan.FromMinutes(5)).ConfigureAwait(false); } public ObservableCollection<Item> Items { get; private set; } private NotifyTask<List<Item>> _rawItemsTask;
三、方案优势验证
- 异步加载深层嵌套:每个ViewModel独立处理自身异步属性,父VM仅负责聚合状态
- 统一聚合状态:通过监听子VM的状态变化,实时计算全局IsBusy/HasError
- 避免臃肿Load方法:加载逻辑拆分到各个VM,每个方法职责单一
- 无重复数据库请求:缓存层统一管理数据,所有VM共享缓存结果
- IoC适配:依赖通过构造函数注入,子VM由IoC容器动态创建
- 线程安全:缓存层用SemaphoreSlim保证并发安全,数据加载用
ConfigureAwait(false)避免UI上下文捕获
内容的提问来源于stack exchange,提问作者Chronicide
相关产品推荐
相关产品推荐

