在Blazor中,StateHasChanged()和InvokeAsync(StateHasChanged)是两种不同的方法。 StateHasChanged()是一个同步方法,用于通知Blazor组件重新渲染。当组件的状态发生变化时,可以调用StateHasChanged()方法来触发重新渲染。 而InvokeAsync(StateHasChanged)是一个异步方法,用于在异步操作完成后,调用StateHasChanged()方法来触发重新渲染。通常情况下,当需要在异步操作完成后更新组件时,可以使用InvokeAsync(StateHasChanged)方法。 总的来说,StateHasChanged()方法是直接触发组件重新渲染的方法,而InvokeAsync(StateHasChanged)方法则是在异步操作完成后触发组件重新渲染的方法。根据具体的需求,可以选择使用适合的方法来更新组件。

69
我知道调用StateHasChanged()方法会通知组件状态已更改,因此应重新渲染。
然而,我有时也会看到其他人的代码中出现await InvokeAsync(StateHasChanged)await InvokeAsync(() => StateHasChanged())这样的内容,但我不确定它们与StateHasChanged()有何不同,以及何时应优先选择其中之一,以及为什么。
我只找到了Blazor文档中的这部分内容,其中提到:

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

我不理解这个。它只是说“...会分派到Blazor的同步上下文”,我对此不满意,什么是“Blazor的同步上下文”?
我尝试过在计时器的Elapsed事件中调用StateHasChanged(),而不是InvokeAsync(StateHasChanged),结果如预期地工作,没有任何问题。我应该调用await InvokeAsync(StateHasChanged)吗?如果是这样,为什么呢?我感觉可能有一些重要的细微差别我不知道。
我还看到过类似InvokeAsync(() => InvokeAsync(Something))的调用,同样的问题,为什么呢?
此外,我有时也看到InvokeAsync()被调用时没有使用await,这是怎么回事?
问题太多了,抱歉! :D
1个回答

64
我尝试过在计时器的Elapsed事件中调用StateHasChanged(),而不是InvokeAsync(StateHasChanged),并且它按预期工作。
那一定是在WebAssembly上。当你在Blazor服务器端尝试这样做时,我会预期会出现异常。StateHasChanged()会检查它是否在正确的线程上运行。
核心问题是渲染和调用StateHasChanged都必须在主(UI)线程上进行。实际上,这是在“同步上下文”上进行的,但就所有目的而言,你可以将其视为单线程,就像在WinForms、WPF和其他GUI中一样。虚拟DOM不是线程安全的。
主要的Blazor生命周期事件(OnInit、AfterRender、ButtonClick)都在那个特殊的线程上执行,所以在你需要在那里调用StateHasChanged()的罕见情况下,可以不用InvokeAsync()。
计时器是不同的,它是一个“外部事件”,所以你不能确定它会在正确的线程上执行。InvokeAsync()将工作委托给Blazor的同步上下文,以确保它在主线程上运行。
但是Blazor WebAssembly只有一个线程,所以暂时外部事件也总是在主线程上运行。这意味着当你在这个调用模式上出错时,你不会注意到任何问题。直到有一天,当Blazor Wasm最终获得真正的线程时,你的代码将会失败。就像你的计时器实验一样。
“Blazor的同步上下文”是什么?
在.net中,同步上下文决定了await之后发生的事情。不同的平台有不同的设置,Blazor的同步上下文很像WinForms和WPF的同步上下文。主要是默认的是.ConfigureAwait(true):在相同的线程/上下文中恢复。
我有时在顶级Blazor Wasm代码中看到.ConfigureAwait(false)。当我们在那里获得真正的线程时,这也会出问题。在从Blazor调用的服务中使用它是可以的,但对于顶级方法来说不行。
最后,await InvokeAsync(StateHasChanged)或await InvokeAsync(() => StateHasChanged())只是关于C#中的lambda表达式,与Blazor无关。第一种简短形式更高效。
有时候我也看到有人在调用InvokeAsync()时没有使用await关键字。
这样做是可以的。这种方式可能比另一种选择更好:将调用方法(比如计时器的OnTick方法)设置为async void。所以在同步代码路径中可以使用这种方式。

谢谢。顺便说一下,关于你的声明“最后,等待InvokeAsync(StateHasChanged或await InvokeAsync(()=> StateHasChanged()仅涉及C#中的lambda,与Blazor无关。”但我的意思是我实际上看到了一个InvokeAsync调用在另一个调用中,就像这里(第42行) - aradalvand
哦,好的,知道了。谢谢!再问一件事:你说“……直到有一天,当 Blazor Wasm 最终拥有真正的线程时,你的代码将失败”,这是真的吗?还是 Blazor WebAssembly 预计始终保持单线程?这又意味着 InvokeAsync 在 Blazor WebAssembly 中始终无关紧要,只在 Blazor 服务器端中才相关。 - aradalvand
2
正在讨论中,我想是这样的:https://github.com/dotnet/aspnetcore/discussions/22885 - H H
2
@HenkHolterman 如果有时 StateHasChanged() 导致错误,那我是不是应该总是使用 await InvokeAsync(StateHasChanged)?这样就像一个无脑的选项。如果不是,为什么? - UserLuke
(晚回复:)不,我不会总是使用它。它会增加一点点额外的开销,但更重要的是:它几乎不应该被需要。我想吸引人们注意那些需要使用它的特殊情况。Invoke()是一个黄色的标志。 - H H
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接