Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When does a blazor component render?

In the diagram below from this page it shows that when an incomplete task is returned by the call to OnInitializedAsync it will await the task and then render the component.

However it seems that what actual happens when an incomplete task is returned is renders the component immediately, and then renders it again once the incomplete task completes.

enter image description here

An example later in the page seems to confirm this. If the component was not rendered immediately after the call to OnInitializedAsync, and instead only rendered for the first time after the Task returned had been completed you would never see the "Loading..." message.

OnParametersSetAsync behavior appears the same. It renders once immediately when an incomplete task is returned, and then again once that task has completed.

Am I misunderstanding the render lifecycle, or is this an error in the documentation?

Thanks

@page "/fetchdata"
@using BlazorSample.Data
@inject WeatherForecastService ForecastService

<h1>Weather forecast</h1>

<p>This component demonstrates fetching data from a service.</p>

@if (forecasts == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <table class="table">
        <!-- forecast data in table element content -->
    </table>
}

@code {
    private WeatherForecast[]? forecasts;

    protected override async Task OnInitializedAsync()
    {
        forecasts = await ForecastService.GetForecastAsync(DateTime.Now);
    }
}
like image 798
innominate227 Avatar asked Jul 19 '26 17:07

innominate227


2 Answers

Short summary

  • Conceptually, Blazor adds two 'free' StateHasChanged calls, one before and one after each lifecycle event and UI event.
  • StateHasChanged only requests an html update, it does not perform one.
  • An update request can only be fulfilled after the event
    or when the main Thread is released by an await
    • but not every await will release the Thread.

So, when you want to make sure the screen gets updated, use

StateHasChanged();    // request 
await Task.Delay(1);  // execute 

Note that Task.Yield() may look better than Delay(1) but it does not work when running on WebAssembly.


Old answer

when an incomplete task is returned it renders the component immediately, and then renders it again once the incomplete task completes.

Yes, that is a possible sequence.

The flowchart shows the steps for showing a component. What is not so clear from the picture is that the actual rendering is not part of this flow, it happens async on the synchronizationcontext. It can happen when your code awaits something.

So we have this basis non-async sequence:

  • Oninitialzed[Async]

  • OnParametersSet[Async]

  • Render

  • OnAfterRender[Async]

But when there is something async in this code-path then there can be one extra Render during the await. More Renders are possible when you call StateHasChanged during this flow.

like image 171
Henk Holterman Avatar answered Jul 21 '26 05:07

Henk Holterman


To fully answer your question we need to delve into the ComponentBase code.

Your code is running in the async world where code blocks can yield and give control back to the caller - your "incomplete task is returned".

SetParametersAsync is called by the Renderer when the component first renders and then when any parameters have changed.

public virtual Task SetParametersAsync(ParameterView parameters)
{
    parameters.SetParameterProperties(this);
    if (!_initialized)
    {
        _initialized = true;
        return RunInitAndSetParametersAsync();
    }
    else
        return CallOnParametersSetAsync();
}

RunInitAndSetParametersAsync is responsible for initialization. I've left the MS coders' comments in which explains the StateHasChanged calls.

private async Task RunInitAndSetParametersAsync()
{
    OnInitialized();
    var task = OnInitializedAsync();

    if (task.Status != TaskStatus.RanToCompletion && task.Status != TaskStatus.Canceled)
    {
        // Call state has changed here so that we render after the sync part of OnInitAsync has run
        // and wait for it to finish before we continue. If no async work has been done yet, we want
        // to defer calling StateHasChanged up until the first bit of async code happens or until
        // the end. Additionally, we want to avoid calling StateHasChanged if no
        // async work is to be performed.
        StateHasChanged();
        try
        {
            await task;
        }
        catch // avoiding exception filters for AOT runtime support
        {
            if (!task.IsCanceled)
                throw;
        }
        // Don't call StateHasChanged here. CallOnParametersSetAsync should handle that for us.
    }
    await CallOnParametersSetAsync();
}

CallOnParametersSetAsync is called on every Parameter change.

private Task CallOnParametersSetAsync()
{
    OnParametersSet();
    var task = OnParametersSetAsync();
    // If no async work is to be performed, i.e. the task has already ran to completion
    // or was canceled by the time we got to inspect it, avoid going async and re-invoking
    // StateHasChanged at the culmination of the async work.
    var shouldAwaitTask = task.Status != TaskStatus.RanToCompletion &&
        task.Status != TaskStatus.Canceled;

    // We always call StateHasChanged here as we want to trigger a rerender after OnParametersSet and
    // the synchronous part of OnParametersSetAsync has run.
    StateHasChanged();

    return shouldAwaitTask ?
        CallStateHasChangedOnAsyncCompletion(task) :
        Task.CompletedTask;
}

In the diagram substitute "Render" for StateHasChanged in the code above.

The diagram uses the work "Render", which is a bit misleading. It implies that the UI re-renders, when what actually happens is a render fragment (a block of code that builds the UI markup for the component) is queued on the Renderer's render queue. It should say "Request Render" or something similar.

If the component code that triggers a render event, or calls StateHasChanged, is all synchronous code, then the Renderer only gets thread time when the code completes. Code blocks need to "Yield" for the Renderer to get thread time during the process.

It's also important to understand that not all Task based methods yield. Many are just synchronous code in a Task wrapper.

So, if code in OnInitializedAsync or OnParametersSetAsync yields there's a render event on the first yield and then on completion.

A common practice to "yield" in a block of synchronous code is to add this line of code where you want the Renderer to render.

await Task.Delay(1);

You can see ComponentBase here - https://github.com/dotnet/aspnetcore/blob/main/src/Components/Components/src/ComponentBase.cs

like image 32
MrC aka Shaun Curtis Avatar answered Jul 21 '26 06:07

MrC aka Shaun Curtis