Blazor Timer 调用异步 API 任务来更新 UI

Fra*_*nky 19 blazor blazor-server-side

我正在 Blazor 服务器端页面中设置计时器。目标是每 x 秒调用一次 API,并根据返回值更新 UI。

我得到这个代码:

private string Time { get; set; }

protected override void OnInitialized()
{
    var timer = new System.Threading.Timer((_) =>
    {
        Time = DateTime.Now.ToString();
        InvokeAsync(() =>
        {
            StateHasChanged();
        });
    }, null, 0, 1000);
    base.OnInitialized();
}
Run Code Online (Sandbox Code Playgroud)

这效果很好。UI 每秒更新一次新的时间值。但是,我不知道如何调用异步任务来获取值。我想更换线路:

Time = DateTime.Now.ToString();
Run Code Online (Sandbox Code Playgroud)

一行调用以下函数:

private async Task<string> GetValue()
{
    var result = await _api.GetAsync<StringDto>("/api/GetValue");
    return result.Text;
}
Run Code Online (Sandbox Code Playgroud)

我试过这条线:

Time = GetValue().Result;
Run Code Online (Sandbox Code Playgroud)

但我收到以下错误:

The current thread is not associated with the Dispatcher. Use InvokeAsync() to switch execution to the Dispatcher when triggering rendering or component state.
Run Code Online (Sandbox Code Playgroud)

我需要做什么来调用异步方法?

非常感谢!

Hen*_*man 23

您可能不想 Invoke() GetValue(),那是毫无意义的。您可以像这样实现计时器:

System.Threading.Timer timer;
protected override void OnInitialized()
{
    timer = new System.Threading.Timer(async _ =>  // async void
    {
        Time = await GetValue();
        // we need StateHasChanged() because this is an async void handler
        // we need to Invoke it because we could be on the wrong Thread          
        await InvokeAsync(StateHasChanged);
    }, null, 0, 1000);
}
Run Code Online (Sandbox Code Playgroud)

我使用一个字段来存储计时器,因为您应该处置它,将其添加到 Razor 部分:

@implements IDisposable
Run Code Online (Sandbox Code Playgroud)

这是代码:

public void Dispose()
{
    timer?.Dispose();
}
Run Code Online (Sandbox Code Playgroud)

  • 是的,我可以确认,如果您检查 https://source.dot.net/#Microsoft.AspNetCore.Components/ComponentBase.cs,64e82abf33cdffa6 您会看到“OnInitialized”中没有任何语句。 (2认同)

小智 18

在.NET 6中您可以使用PeriodicTimer类,如下所示

私有字符串时间{获取; 放; }

     protected override async void OnAfterRender(bool firstRender)
{
    if (firstRender)
    {
        using var periodicTimer = new PeriodicTimer(TimeSpan.FromMinutes(10));
        while (await periodicTimer.WaitForNextTickAsync())
        {
          Time=   await GetValue();

            await InvokeAsync(StateHasChanged);
        }
    }
}
     
Run Code Online (Sandbox Code Playgroud)

运行它而不永远绑定 LifeCycle 方法:

@implements IDisposable
Run Code Online (Sandbox Code Playgroud)
     protected override async void OnAfterRender(bool firstRender)
{
    if (firstRender)
    {
        using var periodicTimer = new PeriodicTimer(TimeSpan.FromMinutes(10));
        while (await periodicTimer.WaitForNextTickAsync())
        {
          Time=   await GetValue();

            await InvokeAsync(StateHasChanged);
        }
    }
}
     
Run Code Online (Sandbox Code Playgroud)

  • 我会在这里编辑它,如果你不喜欢它,请回滚。 (2认同)

Isa*_*aac 7

试试这个代码:

  private string Time { get; set; }

protected override void OnInitialized()
{
    base.OnInitialized();
    var timer = new System.Threading.Timer((_) =>
    {

        InvokeAsync( async ()  =>
        {
            Time = await GetValue();
            StateHasChanged();
        });
    }, null, 0, 1000);

}
Run Code Online (Sandbox Code Playgroud)

您的 GetValue 方法应该是:

private async Task<string> GetValue()
{
    return await _api.GetAsync<StringDto>("/api/GetValue");
  
}
Run Code Online (Sandbox Code Playgroud)