Calling async code in a constructor of a viewmodel - confused

JasinCole

Well-known member
Joined
Feb 16, 2023
Messages
66
Programming Experience
1-3
I am having a hard time understanding async/await code in my Winui3 app. I have a data service with the following code which I believe is correct approach using EF

C#:
public async Task<IEnumerable<Employ>> GetCurrentEmployeesAsync()
    {
        return await SageDbContext.Employs
            .Where(e => e.Status == (byte)Status.Current)
            .ToListAsync();
    }

Then I have this in my viewmodel. But it doesn't work as I expect. I've done significant searching but it seems I am missing part of the equation. Why does this block the UI thread? What's the better approach?

C#:
{
    private readonly IUnitOfWork _srvc;
    private readonly Task _initTask;

    [ObservableProperty]
    private IEnumerable<Employ> _employees;

    [ObservableProperty]
    private Employ _selectedEmployee;
    
    [ObservableProperty]
    private string _firstName;

    [ObservableProperty]
    private string _lastName;
    
    public EmployeeViewModel(IUnitOfWork srvc) {
        _srvc = srvc;
        _initTask = LoadEmployees();
    }

    public async Task LoadEmployees()
    {
        Employees = await _srvc.Employees.GetCurrentEmployeesAsync();
    }
}

I also tried to use the TaskNotifier according to microsoft, but regardless of my attempt it always blocks the UI, which probably means I'm and idiot and don't understand async/await properly. Would appreciate a little understanding on this subject.
 
It blocks the UI because you are calling the method synchronously. Notice that you don't have await in front of the method call on line 19.

Please show us your attempts using the task notifier. Shouldn't you be using the dispatcher, not the task notifier?

Anyway, good OO principles point towards a rule of thumb that constructors should not do any work. Constructors should just initialize variables and setup initial state to be able to do work later. You should do work in actual methods. Consider what happens if you do work in a constructor and you hit an exception. How will you know how to clean up properly (if there is no garbage collection, or if you implement IDisposable). Do lazy initialization instead. Take advantage of Lazy<T> where you can.
 
I guess my biggest issue here is no matter how I go about it I can't initialize the field in the constructor. Which I did understand was a bad design choice but google wasn't very helpful in offering a solution. Stackoverflow just seems full of bad design answers...

I've never encountered Lazy<T> are you suggesting something like this? Edit: NVM, you can't do this because non-static LoadEmployees is not accessible prior to object initilization.
C#:
[ObservableProperty]
private Lazy<IEnumerable<Employ>> _employees = new(LoadEmployees(), false);
 
Last edited:
That constructor is expecting a delegate to a function, not the result of a function call. It should be this:
C#:
private Lazy<IEnumerable<Employ>> _employees = new(LoadEmployees, false);
Note there are no parentheses because you're not calling the method.
 
Ah yes, that makes sense to me now. I guess the problem then becomes the LoadEmployees method which uses an injected dependency to the object. That means I can't mark it static.
Is this a situation where I should be applying the factory pattern?

C#:
BaseViewModel viewModel = ViewModelFactory.CreateViewModel(nameof(EmployeeViewModel));
viewmodel.LoadEmployees();

That moves the initialization of the field _employees outside of the constructor and still allows me to use the injected service
 
I'm having a hard time understanding this async and await stuff. I understand that I am supposed await a Task for current method call to not block the current thread.

But I need help understanding exactly what is happening, because my GUI app is still being blocked and all my attempts confuse me more

C#:
async Task DoSomething() {
    //Do Work
}

Task task = DoSomething(); //Does the actual work start here async?

//Other stuff that is possibly happening

await task; //I'm assuming this just checks task for completion. if completed move on if uncomplete pause and wait

I believe the above is true, now the problem that I don't understand. How do you call an async method to run async from a method that isn't marked as async? I want to call the object method LoadEmployees() to update the Employee property, where and how can i do that outside of the constructor? I thought possibly the best way to do this would be to subscribe to the Loaded event of the WinUI Page, mark that method as async void (which I believe is ok) and await LoadEmployees there. But this also blocks the UI thread.
C#:
public async Task LoadEmployees() //Where do I call this method to not block the UI?
    {
        Employees = await _srvc.Employees.GetCurrentEmployeesAsync();
    }

What am I missing about async and await
 
Your Loaded event handler should look something like:
C#:
async void MyForm_Load(object sender, RoutedEventArgs e)
{
    await LoadEmployees();
}
 
I tried that
Your Loaded event handler should look something like:
C#:
async void MyForm_Load(object sender, RoutedEventArgs e)
{
    await LoadEmployees();
}

But the UI is still freezing. The Page loads, I see the white background that is empty. Then a pause and the entire UI is frozen. Clicks that I make on the NavigationView are queued and execute once the data populates inside the page of the UI. I don't get it... Maybe my expectation here is wrong, but I assume I should be able to at least drag the window while the employees is loading, but not even that.

It makes me wonder if this is an issue outside of async code?
 
Well you could try loading the data in another thread instead of using async/await, but as some point you'll need to interact with the UI thread to get the data that was loaded into memory into the UI itself.
 
You also asked for my attempt using TaskNotifier in MVVM Toolkit. I followed the documentation (ObservableObject - .NET Community Toolkit) and this simply just deadlocks the app.


The most aggrevating point to all of this is I do not have any clear expectation what the end result should be. Suffice to say nothing is making a whole lot of sense atm

I've also read Stephen Cleary blog about async/await Async OOP 3: Properties and looking at data-bound example he suggest using NotifyTaskCompletion, which according to the microsoft docs linked above TaskNotifier is supposed to replace. ugh
 
I think I was onto something with it being outside of the async code. While poking around I noticed my memory consumption was through the roof. Everytime I switched pages in the NavigationView my memory consumption was continuing to rise. When I would click on the Employee Page my memory consumption would go up 200MB each time the page was viewed. Seems I am not cleaning up memory that should be disposed of?

But I did notice that when I turned off debugging diagnostic tools when switching to the Employee page I was able to drag the window after the page had loaded but the employee list wasn't loaded. As soon as that async call returned the window froze and the employees loaded into the page. So it does seem to me that the async code is working.

Now how do I track down what is causing the memory consumption?
 
Back
Top Bottom