Home > Back-end >  Blazor StateHasChanged and child parameters (`await Task.Run(StateHasChanged);` vs `await InvokeAsyn
Blazor StateHasChanged and child parameters (`await Task.Run(StateHasChanged);` vs `await InvokeAsyn

Time:12-02

I recently asked a question regarding the difference between await Task.Run(StateHasChanged); and await InvokeAsync(StateHasChanged); in Blazor wasm here.

The conclusion was that await Task.Run(StateHasChanged); was incorrect and should be avoided; using it would produce the same results as await InvokeAsync(StateHasChanged); however would fall over when threads are available (The accepted answer explains in detail).

I've updated my codebase to use await InvokeAsync(StateHasChanged);, however I've discovered there is actually a difference in outcome between the two.

Here's a minimal reproduction of the issue in my application:

Parent

<h1>Parent: @title</h1>

<button class="btn btn-primary" @onclick="() => SetBool(true)">True</button>
<button class="btn btn-primary" @onclick="() => SetBool(false)">False</button>

<Child Bool="@Bool" @ref="Child"></Child>

@code {
    private bool Bool = false;
    private Child Child;
    private string title;

    private async void SetBool(bool name)
    {
        Bool = name;
        title = Bool ? "True" : "False";
        // NOTE: This will work as expected; Child will be updated with the Parent
        // await Task.Run(StateHasChanged);
        // NOTE: This will only update the Child the second time it is clicked
        await InvokeAsync(StateHasChanged);
        await Child.Trigger();
    }

}

Child

<h3>Child: @title</h3>

@code {
    [Parameter]
    public bool Bool { set; get; } = false;
    private string title;

    public async Task Trigger()
    {
        title = Bool ? "True" : "False";
        await InvokeAsync(StateHasChanged);
    }
}

Clicking either the True or False button in the parent should update the Bool value in both the parent and child. Note that the title variable is only used for a visual display of the Bool value.

Using await Task.Run(StateHasChanged); will cause both the parent and child's state to be updated at the same time. On the other hand await InvokeAsync(StateHasChanged); will update the parent, but not the child; it takes two clicks of the a button to get the respective value in the child component.

Is there an issue with how I'm passing this value to the child component?

Note that using await Task.Run(StateHasChanged); isn't an option; doing so means I can't test the component with bUnit.

The reproduction code is available here.

CodePudding user response:

You aren't passing the new value into the Child component. This is one way you could make it work.

private async void SetBool(bool name)
{
    ...
                
    await InvokeAsync(StateHasChanged);
    await Child.Trigger(Bool); // pass the new value
}

public async Task Trigger(bool newValue)
{
    title = newValue ? "True" : "False";
    await InvokeAsync(StateHasChanged);
}

CodePudding user response:

You have an async void in async void SetBool(bool name). That explains the difference you might see with Task.Run.

  1. You will (almost) never need async void in Blazor. It supports awaitable eventhandlers. Use async Task SetBool(bool name) ... and the results should become deterministic.

  2. You don't need the InvokeAsync() here. Everything runs on the main thread.
    You only need it with Blazor Server in code that is executed by Task.Run().

  3. You don't need StateHasChanged() everywhere. Only use it to display intermediate results in a method. In the sample code you would kinda need it in Trigger but currently Trigger() itself is not needed. The Child should rerender when Bool changes without any help from you. The async void may have led you to believe otherwise.

  • Related