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

异步延迟初始化使用优化咨询:集合并行初始化方案探讨

异步延迟初始化优化与方案评估

我有一个ViewModel,需要从数据库获取两个数据集合(国家列表、增值税类型列表)。为了避免在构造函数中执行高开销操作,我尝试用Lazy<Task<ObservableCollection<T>>>实现异步延迟初始化,但首次调用SetInvoice时,两个集合的初始化是串行执行的,总共耗时10秒(每个操作5秒)。

我怀疑使用.Result属性的方式有问题,想知道有没有办法优化这个异步延迟初始化方案,实现集合的并行初始化。另外我还提出了一种手动调用InitializeAsync的方案,用Task.WhenAll实现并行初始化,耗时仅5秒,但需要ViewModel的调用者主动执行初始化,不够透明。

希望得到优化异步延迟初始化方案的建议,或者评估两种方案的适用性。


第一种:异步延迟初始化方案

public class MyViewModel
{
    // 从数据库获取数据的服务
    private readonly IDataService _dataService;

    // 通过依赖注入注入服务
    public MyViewModel(IDataService paramDataService)
    {
        _dataService = paramDataService;

        _countriesOc = new Lazy<Task<ObservableCollection<Country>>>(InitializeCountriesAsync);
        _vatsOc = new Lazy<Task<ObservableCollection<VAT>>>(InitializeVatsAsync);
    }

    private readonly Lazy<Task<ObservableCollection<Country>>> _countriesOc;
    public IReadOnlyCollection<Country> Countries => _countriesOc.Value.Result;

    public Country? SelectedCountry;

    private async Task<ObservableCollection<Country>> InitializeCountriesAsync()
    {
        // 模拟耗时操作
        await Task.Delay(5000).ConfigureAwait(false);
        return new ObservableCollection<Country>(await _dataService.GetAllCountriesAsync().ConfigureAwait(false));
    }

    private readonly Lazy<Task<ObservableCollection<VAT>>> _vatsOc;
    public IReadOnlyCollection<VAT> Vats => _vatsOc.Value.Result;

    public VAT? SelectedVat;

    private async Task<ObservableCollection<VAT>> InitializeVatsAsync()
    {
        // 模拟耗时操作
        await Task.Delay(5000).ConfigureAwait(false);
        return new ObservableCollection<VAT>(await _dataService.GetAllVatsAsync().ConfigureAwait(false));
    }

    public void SetInvoice(Invoice paramInvoice)
    {
        SelectedCountry = Countries.FirstOrDefault(x => x.Id == paramInvoice.IdCountry);
        SelectedVat = Vats.FirstOrDefault(x => x.Id == paramInvoice.IdVat);
    }
}

第二种:手动并行初始化方案

public class MyViewModel
{
    // 从数据库获取数据的服务
    private readonly IDataService _dataService;
    private readonly ObservableCollection<Country> _countriesOc = new();
    private readonly ObservableCollection<VAT> _vatsOc = new();

    // 通过依赖注入注入服务
    public MyViewModel(IDataService paramDataService)
    {
        _dataService = paramDataService;
    }

    public Task InitializeAsync()
    {
        return Task.WhenAll(InitializeCountriesAsync(), InitializeVatsAsync());
    }

    public IReadOnlyCollection<Country> Countries => _countriesOc;

    public Country? SelectedCountry;

    private async Task InitializeCountriesAsync()
    {
        // 模拟耗时操作
        await Task.Delay(5000).ConfigureAwait(true);
        var countries = await _dataService.GetAllCountriesAsync().ConfigureAwait(true);
        foreach (var country in countries)
        {
            _countriesOc.Add(country);
        }
    }

    public IReadOnlyCollection<VAT> Vats => _vatsOc;

    public VAT? SelectedVat;

    private async Task InitializeVatsAsync()
    {
        // 模拟耗时操作
        await Task.Delay(5000).ConfigureAwait(true);
        var vats = await _dataService.GetAllVatsAsync().ConfigureAwait(true);
        foreach (var vat in vats)
        {
            _vatsOc.Add(vat);
        }
    }

    public void SetInvoice(Invoice paramInvoice)
    {
        SelectedCountry = Countries.FirstOrDefault(x => x.Id == paramInvoice.IdCountry);
        SelectedVat = Vats.FirstOrDefault(x => x.Id == paramInvoice.IdVat);
    }
}

优化异步延迟初始化方案的建议

原第一种方案串行执行的核心原因:调用Countries属性时,会触发_countriesOc.Value启动初始化任务,.Result会阻塞等待任务完成;之后调用Vats属性才会启动第二个任务,导致两个任务串行执行。

优化方案1:构造时启动并行任务

在ViewModel构造阶段就启动两个加载任务,实现并行执行,首次访问属性时只需等待任务完成:

public class MyViewModel
{
    private readonly IDataService _dataService;
    private readonly Task<ObservableCollection<Country>> _countriesTask;
    private readonly Task<ObservableCollection<VAT>> _vatsTask;

    public MyViewModel(IDataService paramDataService)
    {
        _dataService = paramDataService;
        // 构造时直接启动两个并行任务
        _countriesTask = InitializeCountriesAsync();
        _vatsTask = InitializeVatsAsync();
    }

    private ObservableCollection<Country>? _countries;
    public IReadOnlyCollection<Country> Countries
    {
        get
        {
            if (_countries == null)
            {
                // 用GetAwaiter().GetResult()替代.Result,降低死锁风险
                _countries = _countriesTask.GetAwaiter().GetResult();
            }
            return _countries;
        }
    }

    public Country? SelectedCountry;

    private async Task<ObservableCollection<Country>> InitializeCountriesAsync()
    {
        await Task.Delay(5000).ConfigureAwait(false);
        return new ObservableCollection<Country>(await _dataService.GetAllCountriesAsync().ConfigureAwait(false));
    }

    private ObservableCollection<VAT>? _vats;
    public IReadOnlyCollection<VAT> Vats
    {
        get
        {
            if (_vats == null)
            {
                _vats = _vatsTask.GetAwaiter().GetResult();
            }
            return _vats;
        }
    }

    public VAT? SelectedVat;

    private async Task<ObservableCollection<VAT>> InitializeVatsAsync()
    {
        await Task.Delay(5000).ConfigureAwait(false);
        return new ObservableCollection<VAT>(await _dataService.GetAllVatsAsync().ConfigureAwait(false));
    }

    public void SetInvoice(Invoice paramInvoice)
    {
        SelectedCountry = Countries.FirstOrDefault(x => x.Id == paramInvoice.IdCountry);
        SelectedVat = Vats.FirstOrDefault(x => x.Id == paramInvoice.IdVat);
    }
}

优化方案2:真正的延迟并行初始化

如果希望严格做到"首次使用才启动任务",可以用一个统一的初始化触发点,同时启动两个并行任务:

public class MyViewModel
{
    private readonly IDataService _dataService;
    private Task? _initializationTask;
    private ObservableCollection<Country>? _countries;
    private ObservableCollection<VAT>? _vats;

    public MyViewModel(IDataService paramDataService)
    {
        _dataService = paramDataService;
    }

    private async Task InitializeAsync()
    {
        // 同时启动两个并行任务
        var countriesTask = InitializeCountriesAsync();
        var vatsTask = InitializeVatsAsync();
        await Task.WhenAll(countriesTask, vatsTask).ConfigureAwait(false);
        _countries = await countriesTask.ConfigureAwait(false);
        _vats = await vatsTask.ConfigureAwait(false);
    }

    private async Task<ObservableCollection<Country>> InitializeCountriesAsync()
    {
        await Task.Delay(5000).ConfigureAwait(false);
        return new ObservableCollection<Country>(await _dataService.GetAllCountriesAsync().ConfigureAwait(false));
    }

    private async Task<ObservableCollection<VAT>> InitializeVatsAsync()
    {
        await Task.Delay(5000).ConfigureAwait(false);
        return new ObservableCollection<VAT>(await _dataService.GetAllVatsAsync().ConfigureAwait(false));
    }

    public IReadOnlyCollection<Country> Countries
    {
        get
        {
            // 首次访问时启动统一初始化任务
            _initializationTask ??= InitializeAsync();
            _initializationTask.GetAwaiter().GetResult();
            return _countries!;
        }
    }

    public IReadOnlyCollection<VAT> Vats
    {
        get
        {
            _initializationTask ??= InitializeAsync();
            _initializationTask.GetAwaiter().GetResult();
            return _vats!;
        }
    }

    public void SetInvoice(Invoice paramInvoice)
    {
        SelectedCountry = Countries.FirstOrDefault(x => x.Id == paramInvoice.IdCountry);
        SelectedVat = Vats.FirstOrDefault(x => x.Id == paramInvoice.IdVat);
    }
}

三种方案的适用性评估

原异步延迟初始化方案

  • 缺点:串行加载耗时翻倍;.Result存在UI线程死锁风险。
  • 适用场景:两个集合加载有依赖顺序,或其中一个集合极少被使用的场景。

手动并行初始化方案

  • 优点:并行加载性能最优;调用者可灵活控制初始化时机(比如UI启动时后台预加载)。
  • 缺点:需要调用者主动调用InitializeAsync,增加使用复杂度;遗漏调用会导致集合为空引发异常。
  • 适用场景:ViewModel生命周期明确,调用者可可靠触发初始化的场景(如MVVM框架的视图加载钩子)。

优化后的异步延迟初始化方案

  • 优点:自动并行加载,无需调用者干预;真正的延迟初始化(首次使用才启动任务);避免串行加载的性能损耗。
  • 缺点:若ViewModel创建后从未使用集合,会浪费少量资源启动任务(可结合Lazy进一步优化);需注意GetAwaiter().GetResult()的死锁风险(初始化方法中用ConfigureAwait(false)可缓解)。
  • 适用场景:追求透明性与性能的大多数MVVM场景,是首选方案。

内容的提问来源于Stack Exchange,提问作者Álvaro García

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 08:54:52