Jakub Vojtašák
Jakub Vojtašák

Reputation: 47

Blazor - OnAfterRenderAsync called again even previous is not finished

I run into problem with multiple times called OnAfterRenderAsync(bool firstRender) even when a previous run of the same method in the same component is not yet finished.

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    if (firstRender)
    {
        _jsModule = await JsRuntime.LoadJSModule(this);
        await _jsModule!.InvokeVoidAsync("doLogic");
    }

    await _jsModule!.InvokeVoidAsync("doSomethingElse");
}

What the problem is:

  1. On OnAfterRenderAsync runs first time with parameter firstRender = true
  2. It goes inside first condition
  3. I run StateHasChanged in parent component
  4. On AfterRenderAsync is called second time with parameter firstRender = false, even first run is still running and it's inside first condition
  5. Second run skipped first condition and goes directly to logic after, but _jsModule is still null here. So it fails

How this situation should be handled correctly? I tried use Semaphore, and then it works fine. But I think that this situation should be handled somehow by Blazor itself?

I found this problem with one component till now, because I run StateHasChanged in very short period of time.

I can imagine that it can happens also in other components and should be solved generally. Do you have some ideas or do I miss something?

Upvotes: 0

Views: 906

Answers (1)

MrC aka Shaun Curtis
MrC aka Shaun Curtis

Reputation: 30410

OnAfterRender{Async} is a UI Event. It's not an intrinsic part of the lifecycle. It's called after component renders. When it gets invokes is dependant on load on the SynchronisationContext running the Blazor UI context.

You can assign the load method to a global variable and then await it like this:

@page "/counter"

<PageTitle>Counter</PageTitle>

<h1>Counter</h1>

<p role="status">Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;
    private Task<bool> _moduleLoad = Task.FromResult(true);

    private void IncrementCount()
    {
        currentCount++;
    }

    protected override async Task OnParametersSetAsync()
    {
        await Task.Delay(1000);
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        // Use this if you want to bail out if OnAfterRenderAsync is already running
        if (!_moduleLoad.IsCompleted)
        {
            Console.WriteLine("OnAfterRenderAsync bailed out");
            return;
        }

        if (firstRender)
            _moduleLoad = DoSomethingSlow();

        var x = await _moduleLoad;
        var y = await DoSomethingElse();
    }

    private async Task<bool> DoSomethingSlow()
    {
        Console.WriteLine("DoSomethingSlow started");
        await Task.Delay(2000);
        Console.WriteLine("DoSomethingSlow completed");
        return true;
    }

    private async Task<bool> DoSomethingElse()
    {
        Console.WriteLine("DoSomethingElse started");
        await Task.Delay(10);
        Console.WriteLine("DoSomethingElse completed");
        return true;
    }
}

What you get is:

DoSomethingSlow started
OnAfterRenderAsync bailed out
DoSomethingSlow completed
DoSomethingElse started
DoSomethingElse completed

Upvotes: 1

Related Questions