9

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:

  • Why use await Task.Delay(1) - when would I use this technique, what is the use case?
  • 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?
  • Any difference between Task.Delay(1) and Task.Yield()?
7
  • twitter.com/nick_craver/status/1021003343777452032 Commented Nov 18, 2022 at 2:00
  • Could you include a small list of questions with answers that suggest the await Task.Delay(1) as a solution to a problem? Commented Nov 18, 2022 at 2:01
  • @TheodorZoulias Ok that's a decent approach, I'll try compile a list of interesting places where this is used, maybe that will shed light on the technique. Commented Nov 18, 2022 at 2:16
  • Thanks @aybe that's an interesting thread. I think the issue for blazor(wasm) specifically is something to do with how the render queue works. Commented Nov 18, 2022 at 2:19
  • Search blazor source code for occurrences, maybe you'll find some hints: github.com/dotnet/aspnetcore. Press period key to open VS Code within web browser. Commented Nov 18, 2022 at 3:04

3 Answers 3

10

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.

Sign up to request clarification or add additional context in comments.

2 Comments

I'm curious about this. In Blazor Server you can use Task.Run() and surround it with code to enable/disable your loading screen rendering (and it'll work, from what I've seen). Can the same not be done on WASM?
@micka190: I generally recommend not using Task.Run on the server side, especially if it's doing some form of fire-and-forget. AFAIK WASM right now can't use Task.Run in the sense of queueing work to the thread pool, because there's no browser thread pool (yet). So I think Task.Run is essentially the same as a setTimeout(0 in JS; it just queues the work to run on the UI thread when it's available.
5
  • 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.

A typical usage where you will need this, note the disabled="@isBusy":

<button class="btn btn-primary" disabled="@isBusy" @onclick="DoSomething">Click me</button>

@code {
    bool isBusy = false;

    private async Task DoSomething()
    {
        if (isBusy) return; // a click might slip through

        isBusy = true;

        try 
        {
            StateHasChanged();    // not really needed here but cheap
            await Task.Delay(1);
            
            // do some actual work, sync or async
        }
        finally
        {
            isBusy = false;
        }
    }
}

  • 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.

4 Comments

"..don't rely on an await call being really async and guarantee it with a Task.Delay(1)" -- I don't think that the Task.Delay(1) guarantees anything. Theoretically it can have zero effect. The current thread might get suspended by the OS just after the Task.Delay(1) task has been created, and when it is resumed again a few milliseconds later it may find that the Task has already been completed. In that case the await will complete synchronously, and the code will behave like it encountered an await Task.CompletedTask. I don't know if this scenario is practically possible though.
So you are the one behind this!? It deserves a name... "Henk's Hack" maybe? It works (is necessary) for me in a number of places, but until now I didn't understand why. Thanks Henk!
Just found that the docs show something similar to this in "An asynchronous handler involves multiple asynchronous phases".
It is now mentioned in the ms docs: StateHasChanged, favoring Yield(). Take your chances.
0

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);   
}

2 Comments

Thanks enet for your point of view. I've tried my best to avoid this technique, but on more than one occasion it was the only thing that worked for me - to let the renderer work through the jobs on the render queue. The next time I encounter this situation, I'll ping you here with a link to a separate question and maybe you can show me another way to do it.
If using await Task.Delay(1); is bad practice like you say, then show your index.razor using correct practice.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.