Jason Batson
Jason Batson

Reputation: 21

How to correctly create an async method in Blazor?

I am trying to create an async method in Blazor that allows me to run computationally-heavy code asynchronously and then update a component when done, all while keeping the page responsive. The difference between this question and other questions about async in Blazor, is that I am creating a method that takes a long time versus using something like HttpClient's download methods.

The components:

page.blazor

@inject ClassA classA

<button class="btn btn-primary" @onclick="@doWork">Do Work</button>
<Text>@log</Text>

@code {
   async Task doWork()
   {
      log = await classA.doWork();
   }
}

C# classes

public class ClassA
{ 
   public async Task<string> doWork()
   {
      ClassB classB = new ClassB();
      var result = await classB.Execute();
      return result;
   }
}

public class ClassB
{ 
   // This method is meant to take a long time and execute asynchronously
   public async Task<string> Execute()
   {
      string k = "";
      for (int i = 0; i < 10000000; i ++) 
      {
         k = i.ToString();
      }
      return k;
   }
}

Note: If I change the code in page.blazor to

@code {
   async Task doWork()
   {
        await Task.Delay(10000);
        log = "Done";
   }
}

it works properly.

Upvotes: 2

Views: 12342

Answers (2)

Martin VU
Martin VU

Reputation: 151

Based on https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.run?view=net-7.0

You can do:

public async Task<string> doWork() { return await Task.Run(()=> yourwork()); }

Upvotes: 1

MrC aka Shaun Curtis
MrC aka Shaun Curtis

Reputation: 30330

Updated Answer - see @HenkHolterman's comment.

This code block isn't correct. In Visual Studio you will get the warning shown below.

   public async Task<string> Execute()
   {
      string k = "";
      for (int i = 0; i < 10000000; i ++) 
      {
         k = i.ToString();
      }
      return k;
   }

enter image description here

It should look like this:

   public Task<string> Execute()
   {
      string k = "";
      for (int i = 0; i < 10000000; i ++) 
      {
         k = i.ToString();
      }
      return Task.FromResult(k);
   }

You don't make something Async by wrapping it in a Task and telling it to run async. Execute is a synchronous block of code wrapped in a Task. There's no yielding, it just blocks the thread until completion - just like Thread.Sleep(1000).

In Blazor Web Assembly there's ONE thread at present. You can do all the awaits you like on your code. Unless there's a true yield, there's no thread time for the UI and no updates: it all happens at the end!

On the other hand, Task.Delay(xxx) yields. In simple terms, the Task gets moved down the queue by the thread scheduler letting other code already in the queue execute.

Here's a version of your iterator that will yield.

        public async Task<string> Execute()
        {
            string k = "";
            var x = 0;
            for (int i = 0; i < 10000000; i++)
            {
                k = i.ToString();
                if (x > 100)
                {
                    await Task.Delay(1);
                    x = 0;
                }
                x++;
            }
            return k;
        }

And here's a test page:

@page "/"

<PageTitle>Index</PageTitle>

<h1>Async Test</h1>

<div class="p-2">
    Time: @message
</div>
<div class="p-2">
    <button class="btn btn-primary" disabled=@go @onclick=ButtonClick>@buttonMessage</button>
</div>
<div class="p-2">
    <button class="btn btn-secondary" @onclick=UpdateTime>Get Time</button>
</div>


@code {
    private bool go = false;

    private string disabled = "";

    private string buttonMessage => go ? "Running" : "Go";


    private string message = DateTime.Now.ToLongTimeString();

    private void UpdateTime()
    {
        message = DateTime.Now.ToLongTimeString();
    }

    private async Task ButtonClick()
    {
        go = true;
        await this.Execute();
        go = false;
    }

    public async Task<string> Execute()
    {
        string k = "";
        var x = 0;
        for (int i = 0; i < 100000000; i++)
        {
            k = i.ToString();
            if (x > 100)
            {
                await Task.Delay(1);
                x = 0;
            }
            x++;
        }
        return k;
    }
}

Upvotes: 3

Related Questions