【问题标题】:Screen rendering issues when using IJSRuntime Blazor使用 IJSRuntime Blazor 时的屏幕呈现问题
【发布时间】:2021-09-01 00:43:53
【问题描述】:

首先,我很抱歉我的英语水平不好。

我目前正在通过 dotnet core 3.1 blazor 开发一个 web 项目。

如下面的源码,我使用IJSRuntime来调用一个Javascript函数,需要很长时间。

[Inject]
IJSRuntime JSRuntime { get; set; }
private async Task BtnDisplay()
    await JSRuntime.InvokeVoidAsync("RefreshWebJS", Data);
}

Javascript函数需要很长时间,所以我在下面添加了源以添加微调器。

private async Task BtnDisplay()
{
    showLoadingImg = true; // add
    await JSRuntime.InvokeVoidAsync("RefreshWebJS", Data);
    showLoadingImg = false;
}

剃须刀页面上的微调器定义如下:

@if (showLoadingImg == true)
{
    <div style="position: absolute; top: 0px; left: 0px; width: 100%; height: 100%; text-align: center;">
    <img src="/images/LoadingImg.gif" style="position: absolute; top: 50%; left: 50%;" />
    </div>
}

“StateHasChanged()”或“await InvokeAsync(() => StateHasChanged())”也不起作用。

当我使用 Task 而不是 JSRuntime 时效果很好。

await Task.Delay(1000); 

为什么我使用 JSRuntime.InvokeVoidAsync 不起作用?

感谢您阅读难读的英文。

【问题讨论】:

  • 您是否尝试过将调用 JSRuntime 放在自己的任务中并使用 continueWith 来显示Loading = false?
  • 是的,我尝试了“continueWith”,但没有成功。 Magoo 先生建议的 'Task.delay(1)' 方法效果很好

标签: blazor asp.net-core-3.1 asp.net-core-signalr blazor-jsinterop


【解决方案1】:
private async Task BtnDisplay()
{
    showLoadingImg = true; // add
    await Task.Delay(1);
    await JSRuntime.InvokeVoidAsync("RefreshWebJS", Data);
    showLoadingImg = false;
}

Blazor 会在您在异步任务中 await 的第一个 Task 完成时自动重新渲染。

因此,如果第一个 Task 是您长时间运行的进程,那么在完成之前它不会重新渲染。

添加await Task.Delay(1) 是一种允许在长时间运行进程之前进行渲染的简单方法。

延伸阅读:https://github.com/dotnet/aspnetcore/issues/22159

这是 Blazor 工作方式的已知功能,Blazor 的创建者也在该线程中推荐了这种方法(尽管他更喜欢 await Task.Yield() - 我总是忘记使用它!)

【讨论】:

  • 这行得通,但听起来像一个 hacky 解决方案。或者,您可以使按钮事件处理程序立即返回,从而在新任务中启动长时间运行的 js 方法时重新呈现 UI,该方法将在完成后重新同步。
  • 如果您将理解 Blazor 的工作原理并充分利用它称为 hacky,那么可以肯定。你有你的代码示例作为答案吗?
  • 无意冒犯,您的方法完全有效,只是我认为以这种方式使用虚拟任务似乎是一种解决方法。是的,稍后我会提供一个示例,当我有更多时间时。
  • 现在,随着 GitHub 问题的澄清和对 Task.Yield() 的提及,我想我不得不向你道歉,因为我很快就得出了结论。我不知道这是推荐的方式,谢谢分享!这种“假”延迟只是以某种方式触发了我,因为我看到这些东西在不同的上下文中被以奇怪的方式使用。 Task.Yield() 看起来非常合理,并且会比启动一个新任务来运行 js 调用的方法更简洁。
  • 遗憾的是Task.Yield() 并不总是能做到这一点。
【解决方案2】:

正如之前的评论中提到的,还有一种使用同步事件处理程序的方法,它在新任务中启动 JS 互操作并在此之后立即返回。您必须确保在此新任务完成后重新同步它,使用await InvokeAsync(StateHasChanged)

private bool ShowLoading = false;

private void HandleButtonClick()
{
    ShowLoading = true;
    Task.Run(async () => await CallLongRunningJs())
        .ContinueWith(t => { ShowLoading = false; InvokeAsync(StateHasChanged); });
}

private async Task CallLongRunningJs()
{
    await jsRuntime.InvokeVoidAsync("longRunningJsFunc", "data...");
}

它比 Magoo 先生提出的 Task.Yield() 方法更冗长,但我认为为了完整起见,最好在此提及。

【讨论】:

  • Task.Run() 在 WebAssembly 上并没有真正做任何事情,并且是一个不优化的服务器端。并且 ContinueWith() 已过时。 async/await 的全部意义在于能够像 @MisterMagoo 那样用一种清晰的方法来编写它。 “立即退货”没有任何好处。
  • @HenkHolterman 感谢您提供此信息。我没有考虑 Blazor WASM,我主要使用 Blazor Server。我没有声称立即返回有任何额外的好处,除了这会导致 UI 在任务完成之前重新呈现。 - 至少在 Blazor 服务器上。顺便说一句,你从哪里得到这些信息的 ContinueWith 已经过时了?是否有一些官方声明或者您的意思是某种普遍共识?
  • 好吧,“在任务完成之前重新渲染”是一种主张,但我不明白这一点。您如何评价这个与其他答案的复杂性(或可读性)?我会寻找关于 ContinuWith 的链接,但这不是官方的。只是常识。
  • @HenkHolterman 这个问题正是关于尝试在长时间运行的任务执行之前显示加载指示。这才是重点。是的,与其他解决方案相比,此解决方案可能没有任何其他优势。我也认为它的可读性较低,当然!然而,它是一种适用于 blazor 服务器的解决方案,并且我知道并且以前在问题所陈述的这种情况下使用过。
猜你喜欢
  • 2019-07-01
  • 1970-01-01
  • 2021-10-13
  • 1970-01-01
  • 1970-01-01
  • 2011-03-28
  • 1970-01-01
  • 2011-12-13
  • 2018-10-30
相关资源
最近更新 更多