Blazor中的StateHasChanged()与InvokeAsync(StateHasChanged)
我知道调用该StateHasChanged()方法会通知组件状态已更改,因此它应该重新渲染。
但是,我也看到调用await InvokeAsync(StateHasChanged)或await InvokeAsync(() => StateHasChanged())在其他人的代码中,但我不太明白它与其他人的代码有何不同StateHasChanged()以及应该在何处选择一个,以及为什么.
我能找到的唯一信息是Blazor 文档的这一部分,它说:
如果必须根据外部事件(例如计时器或其他通知)更新组件,请使用 InvokeAsync 方法,该方法分派到 Blazor 的同步上下文。
我不太明白这个。它只是说“......它调度到 Blazor 的同步上下文”,但我对此不太满意!什么是“Blazor 的同步上下文”?
我试过在 a的事件中调用StateHasChanged()- 而不是InvokeAsync(StateHasChanged)- ,它按预期工作,没有任何问题。我应该打电话吗?!如果是这样,为什么?我觉得这里可能有一些我不知道的重要细微差别。TimerElapsedawait InvokeAsync(StateHasChanged)
我也看到过类似的电话InvokeAsync(() => InvokeAsync(Something)),为什么?
另外,我有时也会看到InvokeAsync()没有调用await,那有什么关系?!
回答
我曾尝试在 Timer 的 Elapsed 事件中调用 StateHasChanged() - 而不是 InvokeAsync(StateHasChanged) - 它按预期工作
那一定是在 WebAssembly 上。当您在 Blazor Serverside 上尝试时,我预计会出现异常。StateHasChanged() 检查它是否在正确的线程上运行。
核心问题是渲染和调用 StateHasChanged 都必须在主(UI)线程上发生。DOM 的影子副本不是线程安全的。
主要的 Blazor 生命周期事件(OnInit、AfterRender、ButtonClick)都在该特殊线程上执行,因此在需要 StateHasChanged() 的极少数情况下,可以在没有 InvokeAsync() 的情况下调用它。
Timer 是不同的,它是一个“外部事件”,所以你不能确定它会在正确的线程上执行。InvokeAsync() 将工作委托给 Blazor 的 SynchronizationContext,这将确保它确实在主线程上运行。
但是 Blazor WebAssembly 只有 1 个线程,所以暂时外部事件也总是在主线程上运行。这意味着当你弄错这个 Invoke 模式时,你不会注意到任何东西。直到有一天,当 Blazor Wasm 最终获得真正的线程时,您的代码将失败。与您的 Timer 实验一样。
什么是“Blazor 的同步上下文”?
在 .net 中,同步上下文决定了等待(之后)会发生什么。不同的平台有不同的设置,Blazor 的同步上下文很像 WinForms 和 WPF 的同步上下文。主要是,默认是.ConfigureAwait(true):在同一个线程上恢复。
我有时会.ConfigureAwait(false)在顶级 Blazor Wasm 代码中看到。当我们在那里获得真正的线程时,它也会爆炸。可以在从 blazor 调用的服务中使用,但不适用于顶级方法。
最后, await InvokeAsync(StateHasChanged)或者await InvokeAsync(() => StateHasChanged()只是关于 C# 中的 lambda,与 Blazor 无关。第一种简短形式效率更高一些。
我有时也看到
InvokeAsync()没有调用await
这绝不是一个好主意,尽管它通常会奏效。但它比使调用方法(如 Timer 的 OnTick)成为 更好async void,因此从同步代码中使用它。