在本页的下图中,它显示当调用 OnInitializedAsync 返回未完成的任务时,它将等待该任务,然后渲染组件。
然而,当返回不完整的任务时,实际发生的情况似乎是立即渲染组件,然后在未完成的任务完成后再次渲染它。
页面后面的示例似乎证实了这一点。 如果组件在调用 OnInitializedAsync 后没有立即呈现,而是仅在返回的任务完成后首次呈现,您将永远不会看到“正在加载...”消息。
OnParametersSetAsync 行为看起来相同。 当返回未完成的任务时,它立即渲染一次,然后在该任务完成后再次渲染。
我是否误解了渲染生命周期,或者这是文档中的错误?
谢谢
@page "/fetchdata"
@using BlazorSample.Data
@inject WeatherForecastService ForecastService
<h1>Weather forecast</h1>
<p>This component demonstrates fetching data from a service.</p>
@if (forecasts == null)
{
<p><em>Loading...</em></p>
}
else
{
<table class="table">
<!-- forecast data in table element content -->
</table>
}
@code {
private WeatherForecast[]? forecasts;
protected override async Task OnInitializedAsync()
{
forecasts = await ForecastService.GetForecastAsync(DateTime.Now);
}
}
简短摘要
await
释放时
await
都会释放线程。因此,当您想确保屏幕更新时,请使用
StateHasChanged(); // request
await Task.Delay(1); // execute
请注意,Task.Yield() 可能看起来比 Delay(1) 更好,但在 WebAssembly 上运行时它不起作用。
旧答案
当返回未完成的任务时,它会立即渲染组件,然后在未完成的任务完成后再次渲染它。
是的,这是一个可能的顺序。
流程图显示了显示组件的步骤。从图片中不太清楚的是,实际渲染不是此流程的一部分,它在同步上下文上异步发生。当您的代码
await
有什么东西时,就会发生这种情况。
所以我们有这个基础的非异步序列:
初始化[异步]
OnParametersSet[异步]
渲染
OnAfterRender[异步]
但是,当此代码路径中存在异步内容时,则在
await
期间可能会有一个额外的渲染。当您在此流程中调用 StateHasChanged 时,可以进行更多渲染。
为了完全回答您的问题,我们需要深入研究
ComponentBase
代码。
您的代码在异步世界中运行,其中代码块可以yield并将控制权交还给调用者 - 您的“未完成的任务已返回”。
SetParametersAsync
在组件首次渲染时以及当任何参数发生更改时由渲染器调用。
public virtual Task SetParametersAsync(ParameterView parameters)
{
parameters.SetParameterProperties(this);
if (!_initialized)
{
_initialized = true;
return RunInitAndSetParametersAsync();
}
else
return CallOnParametersSetAsync();
}
RunInitAndSetParametersAsync
负责初始化。 我留下了 MS 编码员的评论,其中解释了 StateHasChanged
调用。
private async Task RunInitAndSetParametersAsync()
{
OnInitialized();
var task = OnInitializedAsync();
if (task.Status != TaskStatus.RanToCompletion && task.Status != TaskStatus.Canceled)
{
// Call state has changed here so that we render after the sync part of OnInitAsync has run
// and wait for it to finish before we continue. If no async work has been done yet, we want
// to defer calling StateHasChanged up until the first bit of async code happens or until
// the end. Additionally, we want to avoid calling StateHasChanged if no
// async work is to be performed.
StateHasChanged();
try
{
await task;
}
catch // avoiding exception filters for AOT runtime support
{
if (!task.IsCanceled)
throw;
}
// Don't call StateHasChanged here. CallOnParametersSetAsync should handle that for us.
}
await CallOnParametersSetAsync();
}
每次参数更改时都会调用 CallOnParametersSetAsync
。
private Task CallOnParametersSetAsync()
{
OnParametersSet();
var task = OnParametersSetAsync();
// If no async work is to be performed, i.e. the task has already ran to completion
// or was canceled by the time we got to inspect it, avoid going async and re-invoking
// StateHasChanged at the culmination of the async work.
var shouldAwaitTask = task.Status != TaskStatus.RanToCompletion &&
task.Status != TaskStatus.Canceled;
// We always call StateHasChanged here as we want to trigger a rerender after OnParametersSet and
// the synchronous part of OnParametersSetAsync has run.
StateHasChanged();
return shouldAwaitTask ?
CallStateHasChangedOnAsyncCompletion(task) :
Task.CompletedTask;
}
在图中,将上面代码中的
StateHasChanged
替换为“渲染”。
该图使用了“Render”这个作品,这有点误导。 这意味着 UI 会重新渲染,而实际发生的情况是渲染片段(为组件构建 UI 标记的代码块)在渲染器的渲染队列中排队。 它应该说“请求渲染”或类似的内容。
如果触发渲染事件或调用
StateHasChanged
的组件代码都是同步代码,则渲染器仅在代码完成时获取线程时间。 代码块需要“让出”以便渲染器在此过程中获得线程时间。
了解并非所有基于任务的方法都会产生效果也很重要。 许多只是任务包装器中的同步代码。
因此,如果
OnInitializedAsync
或 OnParametersSetAsync
中的代码产生了第一个产生的渲染事件,然后在完成时产生渲染事件。
在同步代码块中“yield”的常见做法是在您希望渲染器渲染的位置添加这行代码。
await Task.Delay(1);
您可以在这里看到
ComponentBase
- https://github.com/dotnet/aspnetcore/blob/main/src/Components/Components/src/ComponentBase.cs