【问题标题】:How to correctly create an async method in Blazor?如何在 Blazor 中正确创建异步方法?
【发布时间】:2022-03-01 19:21:07
【问题描述】:

我正在尝试在 Blazor 中创建一个异步方法,该方法允许我异步运行计算量大的代码,然后在完成后更新组件,同时保持页面响应。这个问题与其他关于 Blazor 中异步的问题之间的区别在于,我正在创建一个需要很长时间的方法,而不是使用诸如 HttpClient 的下载方法之类的方法。

组件:

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# 类

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

注意:如果我将 page.blazor 中的代码更改为

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

它工作正常。

【问题讨论】:

  • Task.Run 是你所需要的
  • 在 Blazor WebAssembly 中,这暂时不起作用。对于 CPU 工作,您需要额外的线程,而这里没有它们。 Task.Run() 没有多大帮助,您需要一台服务器和/或将工作切成小块。
  • Task.Delay() 是异步的,用它来模拟 I/O。 Thread.Sleep() 是同步的,你可以用它来模拟 CPU 负载。
  • 您能否确认这是一个 Blazor WASM 应用程序?标签之一是“Blazor-webassembly”,但请务必确定,因为它很重要!
  • 多线程是 .Net 7 Wasm 路线图的一部分。(github.com/dotnet/aspnetcore/issues/…)。虽然它不在第一次预览中。不幸的是,除了在下面的答案中尝试产量(或 Task.Delay(非常短的毫秒跨度)或其他东西)以便您为其余的执行留出时间)之外,除了尝试优化您之外没有什么可做的尽可能多地编码。至少现在

标签: c# .net asynchronous blazor blazor-webassembly


【解决方案1】:

更新的答案 - 请参阅@HenkHolterman 的评论。

此代码块不正确。在 Visual Studio 中,您将收到如下所示的警告。

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

应该是这样的:

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

您不会通过将其包装在 Task 中并告诉它运行 async 来制作异步内容。 Execute 是包装在 Task 中的同步代码块。没有让步,它只是阻塞线程直到完成 - 就像 Thread.Sleep(1000)。

目前在 Blazor Web Assembly 中有 ONE 线程。你可以在你的代码上做你喜欢的所有等待。除非有真正的收益,否则 UI 没有线程时间,也没有更新:这一切都发生在最后!

另一方面,Task.Delay(xxx) 产生。简单来说,任务被线程调度器向下移动到队列中,让队列中的其他代码执行。

这是你的迭代器的一个版本。

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

这是一个测试页面:

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

【讨论】:

  • CPU 密集型代码仅在 Blazor WebAssembly 中运行。 Task.FromResult 解决方案和 await Task.Yield() 版本都没有产生响应式 UI。
  • @JasonBatson:用 Task.Delay(1) 替换 Task.Yield() 并确保它被调用 ~10 次/秒。
  • @JasonBatson 感谢 Henk 的模组。 Henk - 这是 Yield 失败而 Delay 有效的一种情况(仔细想想就很明显了)。我更新了答案以反映模组并添加了一个测试页面。
  • 我有兴趣了解为什么 Task.Delay 有效而不是 Yields?我昨天测试了 Yields 并且它有效,虽然它不是 UI 输入,而是另一个带有 Task.Delay 的任务。谢谢:)
  • @Shuryno。我相信这与线程调度程序实现如何优先考虑它的队列有关。我在某处读到,虽然像往常一样我现在找不到它,但在 DotNetCore 的 UI 上下文中,你不应该假设 UI 的东西会得到优先考虑。使用 Yield,您只需通过调度程序重新组织任务。使用延迟时,您可以重新组织 UI 事件并获得一些空闲时间。我当然可能完全错了!! :-)
猜你喜欢
  • 1970-01-01
  • 2013-04-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-08-13
  • 2012-07-14
  • 2019-01-22
相关资源
最近更新 更多