1

I have a view model with 2 collections, that takes the data from database. For example countries and type of VAT.

I would like to defer the initialization of this collections until I will, becauase if I am not wrong, it is better don't do it in the constructor becuase it is a high cost resource and the constrcutor has to be cheap to create.

So one option that I am trying to use is lazy initialization. this is the documentation.

But I am using a little bit different code, because the methods that get the data from the database are async.

This is my view model:

public class MyViewModel
{
    //Service to get data from database.
    private readonly IDataService _dataService;



    //The service is injected with dependency injection.
    public MyViewModel(IDataService paramDataService)
    {
        _dataService = paramDataService;


        _countriesoOc = new Lazy<Task<ObservableCollection<Contry>>>(InitializeContriesAsync);
        _vatsoOc = new Lazy<Task<ObservableCollection<VAT>>>(InitializeVatsAsync);
    }



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

    public Country? SelectedCountry;


    private async Task<ObservableCollection<MagnitudDTO>> InitializeCountriesAsync()
    {
        //Tset long process
        await Task.Delay(5000).ConfigureAwait(false);
        return new ObservableCollection<Contry>(await _dataService.GetAllContriesAsync().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()
    {
        //Tset long process
        await Task.Delay(5000).ConfigureAwait(false);
        return new ObservableCollection<VAT>(await _dataService.GetAllVatsAsync().ConfigureAwait(false));
    }



    public void SetInvoce(Invoce paramInvoce)
    {
        SelectedCountry = _countriesOc.FirstOrDefault(x => x.Id = paramInvoce.IdCountry);
        SelectedVat = _vatsOc.FirstOrDefault(x => x.Id = paramInvoce.IdVat);
    }
}

I have realize that in this case, when I set the first invoice, it takes 10 seconds, because it seems when it set the country it waits the 5 seconds of the initialization and then continue with the set of the VAT, that takes another 5 seconds.

There is some way to set the selected items in a parallel way? I guess that perhaps I am doing wrong using the .Result property in the collections, but I am not sure.

I would try another option, that is using an intilize method that run all the methods in parallel. This is the solution:

public class MyViewModel
{
    //Service to get data from database.
    private readonly IDataService _dataService;



    //The service is injected with dependency injection.
    public MyViewModel(IDataService paramDataService)
    {
        _dataService = paramDataService;
    }


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


    private readonly ObservableCollection<Country> _countriesOc;
    public IReadOnlyCollection<Cauntry> Coutries => _countriesOc;

    public Country? SelectedCountry;


    private async Task<ObservableCollection<MagnitudDTO>> InitializeCountriesAsync()
    {
        //Tset long process
        await Task.Delay(5000).ConfigureAwait(true);
        _countriesOc.AddRange(await _dataService.GetAllCountriesAsync().ConfigureAwait(true));
    }



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

    public VAT? SelectedVat;


    private async Task<ObservableCollection<VAT>> InitializeVatsAsync()
    {
        //Tset long process
        await Task.Delay(5000).ConfigureAwait(true);
        _vatsOc.AddRange(await _dataService.GetAllVatsAsync().ConfigureAwait(true));
    }



    public void SetInvoce(Invoce paramInvoce)
    {
        SelectedCountry = _countriesOc.FirstOrDefault(x => x.Id = paramInvoce.IdCountry);
        SelectedVat = _vatsOc.FirstOrDefault(x => x.Id = paramInvoce.IdVat);
    }
}

In this case, each object that use the view model, get the instance from dependency injection, but it has to call to the initializeAsync() method to populate the collections.

The advange of this solution it is that the initialization takes 5 seconds, because all the collections are initialize in parallel. But I don't like that each object that consume the view model, has to initialize it. It is less transparent for the consumer that the async lazy solution, but it is much faster. If I have many collections, they are initialized in parallel.

So in sumary, I would like to know if there is some way to improve the async lazy solution to run the initialization in parallel, if not, I guess i would go for the second solution, although each consumer has to call the initalize method always before can use it.

Thanks.

2
  • 3
    Personally I would just make SetInvoce to be async Task and await tasks every time. Commented Nov 13, 2023 at 9:53
  • My VMs all have an InitializeAsync method which are called from the view when the view is ready for presentation (e.g. WPF inside Loaded event of the Window). sample on github Commented Nov 13, 2023 at 11:42

1 Answer 1

1

The second option could be rewritten as:

public class MyViewModel
{
    private readonly IDataService _dataService;
    private readonly Lazy<Task> InitTask;

    public MyViewModel(IDataService paramDataService)
    {
        _dataService = paramDataService;
        InitTask = new Lazy<Task>(Task.WhenAll(IntializeCountriesAsync(), InitializeVatsAsync()));
    }

    // ...
    public void SetInvoce(Invoce paramInvoce)
    {
        InitTask.Value.Wait();
        SelectedCountry = _countriesOc.FirstOrDefault(x => x.Id = paramInvoce.IdCountry);
        SelectedVat = _vatsOc.FirstOrDefault(x => x.Id = paramInvoce.IdVat);
    }
}

Though personally I would highly recommend to follow the advice to don't block on async code and make SetInvoce async too:

public async Task SetInvoce(Invoce paramInvoce)
{
    await InitTask.Value.ConfigureAwait(false);
    SelectedCountry = _countriesOc.FirstOrDefault(x => x.Id = paramInvoce.IdCountry);
    SelectedVat = _vatsOc.FirstOrDefault(x => x.Id = paramInvoce.IdVat);
}

If this is an option though, then I would discard the "sync wrappers" of the first case and just do something like the following:

public async Task SetInvoce(Invoce paramInvoce)
{
    await Task.WhenAll(_countriesoOc.Value, _vatsoOc.Value).ConfigureAwait(false);
    SelectedCountry = _countriesoOc.Result.FirstOrDefault(x => x.Id = paramInvoce.IdCountry);
    SelectedVat = _vatsOc.Result.FirstOrDefault(x => x.Id = paramInvoce.IdVat);
}

Alternatively you could look into utilizing in-memory cache.

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks so much. The last option, discarding sync wrappers, work very good. Just in the Task.WhenAll it is needed to use _countriesOc.Value instad of only _conutries.
@ÁlvaroGarcía was glad to help! P.S. fixed the invocation.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.