简体   繁体   中英

Task.Run then Invoke on main thread alternative using async await ContinueWith?

The following code works perfectly. It shows the spinner on the UI, starts a task using a thread from the threadpool and runs the heavy operation, once complete, logic to hide the spinner executes on the main thread as intended.

    public void LoadCustomers()
    {
        // Update UI to show spinner
        this.LoadingCustomers = true;

        Task.Run(async () =>            
        {
            var customers = await this.custService.GetCustomers();
            // code truncated for clarity

            Device.BeginInvokeOnMainThread(() => 
            {
                // Update UI to hide spinner
                this.LoadingCustomers = false;
            });
        });
    }

My question; Is there a better way to write this logic using ContinueWith/ConfigureAwait options? Using these options seems to block the UI thread. In the example below, shouldn't the UI thread continue running the UI logic (animating the spinner/user input) and then come back to complete the logic inside the ContinueWith?

    public void LoadCustomers()
    {
        // Update UI to show spinner
        this.LoadingCustomers = true;

        this.custService.GetCustomers().ContinueWith((t) =>
        {
            var customers = t.Result;
            // code truncated for clarity

            // Update UI to hide spinner
            this.LoadingCustomers = false;
        });
    }

As requested in the comments, here is the code for GetCustomers. the dbContext is EntityFrameworkCore.

    public async Task<List<CustomerModel>> GetCustomers()
    {
        return await this.dbContext.Customers.ToListAsync();
    }

UPDATE

The answer by FCin is correct, however; the cause root of this seems to be with EFCore and ToListAsync, it isn't running asynchronously.

Proper way of writing such method is to use async/await from start to finish. Right now you are doing fire and forget meaning if there is exception inside Task.Run you will never know about it. You should start from an event handler. This can be whatever, mouse click, page loaded, etc.

private async void MouseEvent_Click(object sender, EventArgs args)
{
    await LoadCustomers();
}

public async Task LoadCustomers()
{
    // Update UI to show spinner
    this.LoadingCustomers = true;

    // We don't need Device.BeginInvokeOnMainThread, because await automatically 
    // goes back to calling thread when it is finished
    var customers = await this.custService.GetCustomers();

    this.LoadingCustomers = false;
}

There is an easy way to remember when to use Task.Run . Use Task.Run only when you do something CPU bound, such as calculating digits of PI.

EDIT: @bradley-uffner suggested to just write the following:

public async Task LoadCustomers()
{
    // Update UI to show spinner
    this.LoadingCustomers = true;

    var customers = await this.custService.GetCustomers();
    // code truncated for clarity

    // you are still on UI thread here
    this.LoadingCustomers = false;
}

How about this:

public async Task LoadCustomers()
{
    // Update UI to show spinner
    this.LoadingCustomers = true;

    await Task.Run(async () =>            
    {
        var customers = await this.custService.GetCustomers();
        // code truncated for clarity
    });

    this.LoadingCustomers = false;
}

The code after await is executed on the current thread so it should work out of the box.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM