lonix
lonix

Reputation: 20489

Why use await Task.Delay(1) in Blazor wasm?

Many SO answers use await Task.Delay(1) to solve various async rendering issues in Blazor (wasm). I've even found a number of places in my own code where doing that "makes it work".

However it's always stated as matter of fact, without a thorough explanation, and I can't find this technique in the docs either.

Some questions:

Upvotes: 6

Views: 3134

Answers (3)

Henk Holterman
Henk Holterman

Reputation: 273169

  • Why use await Task.Delay(1)

To show intermediate results in an eventhandler. The canonical code for a screen update is

  StateHasChanged();    // request a render
  await Task.Delay(1);  // allow the render to execute
  • The docs do not discuss this

It is usually not needed. But there's no argument against using it either. I figured out how to use it when solving a problem like this. And I got some negative feedback, see the comments under those posts.

  • Any difference between Task.Delay(1) and Task.Yield()?

Yes, Task.Yield() looks more sensible but I found it does not always work. See Stephen Cleary's answer here.

Upvotes: 3

enet
enet

Reputation: 45586

Below is a link to a question answered by me and by Henk Holterman, in whose answer he uses await Task.Delay(1);

Run the code, and see the difference, as for instance, using await Task.Delay(1); results in re-rendering the component twice, etc.

Is the use of await Task.Delay(1); necessary? Absolutely not. This is a bad practice that not only results in a second re-rendering of a component, but it may lead to subtle issues with complex code. Blazor offers a list of life-cycle methods which you can capture and use to provide the wanted solutions. No hacking, please. This may prove very expensive in the long run. Create elegant code, not hacking...

UPDATE

The code snippet below describes a use case for the use of Task.Delay, demonstrating a page with a button element with the caption "Save," The requirement is to alter the caption's text to "Saving...," immediately after the user clicks the button, for the duration of saving an employee record in a data store. If you know of other use cases for Task.Delay, please let me know.

Index.razor

@page "/"

<div>
    <button class="btn btn-primary" 
                              @onclick="Save">@caption</button>
</div>


@code 
{
    
    private string caption = "Save";

    private async Task SaveEmployee()
    {
        // Simulate saving an employee's record in database...
        // I use System.Threading.Thread.Sleep instead of a loop 
        // with millions of iterations.
        System.Threading.Thread.Sleep(3000);
        // Retruns completed task
        await Task.CompletedTask;
    }

    private async Task Save()
    {
        caption = "Saving...";
        // Renders here automatically after calling Task.Delay()
            
        await Task.Delay(1000);

        await SaveEmployee();

        caption = "Save";
        // Renders here automatically when SaveEmployee() 
        //complete
    }
}

On the other hand, the following code snipppet demonstrates how to not use Task.Delay, and provides an elegant solution, which is certainly not an hack, and whose additional advantage is that it results in a single rendering of the component... Task.Delay involve a second rendering, mind you...

Note: The code below is an answer to this question

Component.razor

<div @ref="ReferenceToDiv" id="select-@Id" style="background-color: red; width:300px; height: 300px">

 </div>

@code
{
    ElementReference ReferenceToDiv;
    // As you can see, you should call the "adjustPosition" method from the 
    // `OnAfterRenderAsync` method to ensure that the div element has been 
    // rendered. DO Not Re-render In Vain. That is, do not use
    // await Task.Delay(1); to re-render your component

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (opened)
        {
          await jsModule.InvokeVoidAsync("adjustPosition", ReferenceToDiv);                
        } 
    }

    public void OnClick()
    {
        opened = !opened;
       
    }
}

test.js

export function adjustPosition(element) {
    // Should return 300px
    console.log($(element.style.width);   
}

Upvotes: 0

Stephen Cleary
Stephen Cleary

Reputation: 456322

Why use await Task.Delay(1) - when would I use this technique, what is the use case?

It gives the UI a chance to update and redraw in the middle of your code.

The docs do not discuss this (that I could find); is it because it's a hack, or is it a legitimate way to deal with the use case?

It's a hack. But in the specific case of Blazor/WASM (no other Blazor or .NET runtime), there's not a lot of other options. If possible, I'd suggest splitting up your logic so your app isn't doing so much all at once; but sometimes that's not possible (or easy).

Any difference between Task.Delay(1) and Task.Yield()?

Depending on the browser details, yes.

On Windows UI apps, Task.Yield won't work for this because the UI message loop is a priority queue and "run this code" is highest priority. So (again, for Windows UI apps), this would queue the rest of the method and then return to the message loop, which would then continue executing the code instead of refreshing the UI (which are lower-priority messages).

For Blazor/WASM, whether Task.Yield would work or not depends on the browser implementation of its (implicit) message loop. If it has a similar priority queue, then you'd end up with the same problem as a Windows UI where Task.Yield does yield to the message loop but doesn't drain it.

On all platforms, Task.Delay(1) actually queues a timer callback, which is generally enough time to get some UI updates handled before the code continues running.

Upvotes: 8

Related Questions