You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 17:47:08