Blazor 中的 StateHasChanged() 与 InvokeAsync(StateHasChanged)

Posted

技术标签:

【中文标题】Blazor 中的 StateHasChanged() 与 InvokeAsync(StateHasChanged)【英文标题】:StateHasChanged() vs InvokeAsync(StateHasChanged) in Blazor 【发布时间】:2021-03-21 15:29:35 【问题描述】:

我知道调用StateHasChanged() 方法会通知组件状态已更改,因此它应该重新渲染。

但是,我在其他人的代码中也看到了对 await InvokeAsync(StateHasChanged)await InvokeAsync(() => StateHasChanged()) 的调用,但我不太明白它与 StateHasChanged() 有何不同以及应该在哪里选择一个而不是另一个,并且 为什么

我能找到的唯一信息是this part of the Blazor docs,上面写着:

如果必须根据外部事件(例如计时器或其他通知)更新组件,请使用 InvokeAsync 方法,该方法调度到 Blazor 的同步上下文。

我不太明白。它只是说“...调度到 Blazor 的同步上下文”,但我对此不太满意!什么是“Blazor 的同步上下文”?

我尝试在TimerElapsed 事件中调用StateHasChanged() - 而不是InvokeAsync(StateHasChanged),它按预期工作,没有任何问题。我应该打电话给await InvokeAsync(StateHasChanged) 吗?!如果是这样,为什么究竟是什么?我觉得这里可能有一些我不知道的重要细微差别。

我还看到了像InvokeAsync(() => InvokeAsync(Something)) 这样的电话,为什么?

另外,我有时也会看到InvokeAsync() 没有await 被调用,那是怎么回事?!

【问题讨论】:

【参考方案1】:

我尝试在 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 synccontext 很像 WinForms 和 WPF。主要是默认.ConfigureAwait(true):在同一个线程上恢复。

我有时会在*** Blazor Wasm 代码中看到 .ConfigureAwait(false)。当我们在那里获得真正的线程时,它也会爆炸。可以在从 blazor 调用的服务中使用,但不能用于***方法。

最后,await InvokeAsync(StateHasChanged)await InvokeAsync(() => StateHasChanged() 只是 C# 中的 lambda,与 Blazor 无关。第一种短格式效率更高。

我有时也会看到 InvokeAsync() 在没有 await 的情况下被调用

这绝不是一个好主意,尽管它通常会起作用。但它比将调用方法(如 Timer 的 OnTick)设置为 async void 更好,所以从同步代码中使用它。

【讨论】:

谢谢。顺便说一句,关于你的声明 And finally, await InvokeAsync(StateHasChanged or await InvokeAsync(() => StateHasChanged() is just about lambda's in C#, nothing to do with Blazor. 但我的意思是我实际上在另一个调用中看到了 InvokeAsync 调用,如 here(第 42 行) 嗯,这是一个测试,不知道它是干什么用的。它不是“真正的代码”。 哦,好的,很高兴知道。谢谢!还有一件事:你说...Until one day, when Blazor Wasm finally gets real threads, your code will fail,真的要来了吗?还是希望 Blazor WebAssembly 始终保持单线程?这反过来意味着InvokeAsync 在 Blazor WebAssembly 中将始终有效地不相关,并且仅在 Blazor 服务器端相关。 我认为正在讨论中:github.com/dotnet/aspnetcore/discussions/22885 好的,所以还没有决定。谢谢!

以上是关于Blazor 中的 StateHasChanged() 与 InvokeAsync(StateHasChanged)的主要内容,如果未能解决你的问题,请参考以下文章

何时在 Blazor 组件中调用 StateHasChanged()

Blazor 组件在 StateHasChanged() 之后未更新

我应该何时调用 StateHasChanged 以及 Blazor 何时自动拦截某些更改?

Blazor StateHasChanged 无法正常工作

使用 StateHasChanged 从父组件刷新 Blazor 服务器子组件

Blazor 重新渲染组件块元素 [关闭]