Home > Software engineering >  Is this the right way to reconfigure my constructor to use async and await?
Is this the right way to reconfigure my constructor to use async and await?

Time:11-18

I've read a few articles and I'm having a hard time wrapping my head around this. I'm trying to keep the UI of my MAUI app updating while loading some data.

I'd like to accomplish this:

async public DataService()
{
    //initialize stuff here
    await this.GetPayees();
    await this.GetCategories();
    return;
}

I've read that you can't have an async constructor, so I've had to redo how I initialize my class.

public DataService()
{
    //Take this out here
    //this.GetPayees();
    //this.GetCategories();
    return;
}

async public static Task<DataService> BuildDataServiceAsync()
{
    //await them here
    var dataService = new DataService();
    await dataService.GetPayees();
    await dataService.GetCategories();
    return dataService;
}

This has had a cascading effect throughout my code. I have to change return types to Task, and make additional methods async

async public Task<List<Payee>> GetPayees()
{
    //Load arbitrary data, 
    if(Payees.Count != 0) return Payees;
    Payees.Add(new Payee { Id = 0, Name = "Food Lion", DefaultCategoryId = 0, DefaultIsCredit = false });
    Payees.Add(new Payee { Id = 1, Name = "Work Incorporated", DefaultCategoryId = 1, DefaultIsCredit = true });
    Payees.Add(new Payee { Id = 2, Name = "Hardees", DefaultCategoryId = 3, DefaultIsCredit = false });
    Payees.Add(new Payee { Id = 3, Name = "Wal-Mart", DefaultCategoryId = 5, DefaultIsCredit = false });
    Payees.Add(new Payee { Id = 4, Name = "Aldis", DefaultCategoryId = 0, DefaultIsCredit = false });
    Payees.Add(new Payee { Id = 5, Name = "McDonalds", DefaultCategoryId = 3, DefaultIsCredit = false });
    Payees.Add(new Payee { Id = 6, Name = "Harris Teeter", DefaultCategoryId = 0, DefaultIsCredit = false });
    Payees.Add(new Payee { Id = 7, Name = "Shoe Show", DefaultCategoryId = 2, DefaultIsCredit = false });
    Payees.Add(new Payee { Id = 8, Name = "Capital One", DefaultCategoryId = 4, DefaultIsCredit = false });
    Payees.Add(new Payee { Id = 9, Name = "Dicks Sporting Goods", DefaultCategoryId = 6, DefaultIsCredit = false });
    Payees.Add(new Payee { Id = 10, Name = "Amazon", DefaultCategoryId = 7, DefaultIsCredit = false });
    return Payees;
}

async public Task<List<Category>> GetCategories()
{
    if(Categories.Count != 0) return Categories;
    Categories.Add(new Category { Id = 0, Name = "Groceries" });
    Categories.Add(new Category { Id = 1, Name = "Paycheck" });
    Categories.Add(new Category { Id = 2, Name = "Shoes" });
    Categories.Add(new Category { Id = 3, Name = "Fast Food" });
    Categories.Add(new Category { Id = 4, Name = "Credit Card" });
    Categories.Add(new Category { Id = 5, Name = "Supplies" });
    Categories.Add(new Category { Id = 6, Name = "Recreation" });
    Categories.Add(new Category { Id = 7, Name = "Grocery" });
    Categories.Add(new Category { Id = 8, Name = "Gross" });
    Categories.Add(new Category { Id = 9, Name = "Grass" });
    return Categories;
}

I get the complier warning about not having await in those last two methods. Would I really await all the Add()s?

I just got dependency injection working the way I want to, but this all cascades back to my viewmodel, which has my dependencies injected:

public PayeesViewModel(DataService dataService, NavigationService navigationService, ValidationService validationService)
{
    this.dataService = dataService;
    this.navigationService = navigationService;
    this.validationService = validationService;
    Payees = await dataService.GetPayees();
    Categories = await dataService.GetCategories();

    for(int x = 0; x < Payees.Count; x  )
    {
        PayeeDisplay.Add(new PayeeDisplay
        {
            Id = Payees[x].Id,
            Name = Payees[x].Name,
            DefaultCategory = Categories.Find(c => c.Id.Equals(Payees[x].DefaultCategoryId)).Name,
            DefaultCategoryId = Payees[x].DefaultCategoryId,
            DefaultIsCredit = Payees[x].DefaultIsCredit
        });
    }
}

Am I going about this the wrong way? Trying to adopt async/await is literally destroying my project.

CodePudding user response:

The async keyword only enables the use of await. If you don't need to use await, then you don't need async.

List.Add() is not an async method, so you wouldn't use await. So just remove the async keyword; you don't need it.

Asynchronous programming is helpful when the CPU has to wait for an external resource, for example: network requests, requests to file storage, etc. - anything that is not inside the CPU or RAM. Adding an object to a collection is purely an in-memory operation, and so it's not a good fit for asynchronous programming.

I think you would benefit from reading over Microsoft's documentation on Asynchronous programming with async and await. It's quite well-written.


Update:

Since you said that the calls to Add() are just placeholders for SQL access, then yes, you would want to use async for the SQL calls, and the way you did it is correct: create an public static async method that will create an instance of the class for you. This is called a "factory method." You can even go a step further and make the constructor private so you can enforce the use of the factory method:

private DataService() {}

public static async Task<DataService> BuildDataServiceAsync()
{
    var dataService = new DataService();
    await dataService.GetPayees();
    await dataService.GetCategories();
    return dataService;
}

Having a private constructor is important if GetPayees() and GetCategories() must be called for the object to be in a usable state.

  • Related