异步延迟初始化使用优化咨询:集合并行初始化方案探讨
异步延迟初始化优化与方案评估
我有一个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
相关产品推荐
相关产品推荐

