Blazor 中的 StateHasChanged() 与 InvokeAsync(StateHasChanged)

StateHasChanged() vs InvokeAsync(StateHasChanged) in Blazor

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

不过,我也看到别人的代码中调用了await InvokeAsync(StateHasChanged)或者await InvokeAsync(() => StateHasChanged()),但是不太明白它和StateHasChanged()有什么不同,应该选哪里在另一个之上,为什么.

我能找到的唯一信息是 this part of the Blazor docs,它说:

In the event a component must be updated based on an external event, such as a timer or other notifications, use the InvokeAsync method, which dispatches to Blazor's synchronization context.

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

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

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

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

I have tried calling StateHasChanged() - instead of InvokeAsync(StateHasChanged) - in a Timer's Elapsed event, and it works as expected

那一定是在 WebAssembly 上。当您在 Blazor Serverside 上尝试时,我会期望出现异常。 StateHasChanged() 检查它是否 运行 在正确的线程上。

核心问题是渲染和​​调用 StateHasChanged 都必须在主 (UI) 线程上发生。 DOM 的卷影副本不是线程安全的。

主要的 Blazor 生命周期事件(OnInit、AfterRender、ButtonClick)都在该特殊线程上执行,因此在极少数情况下您需要 StateHasChanged() 时可以在没有 InvokeAsync() 的情况下调用它。

Timer 则不同,它是一个“外部事件”,因此您无法确定它会在正确的线程上执行。 InvokeAsync() 将工作委托给 Blazor 的 SynchronizationContext,这将确保它在主线程上执行 运行。

但是 Blazor WebAssembly 只有 1 个线程,所以暂时外部事件也总是在主线程上 运行。这意味着当您将此 Invoke 模式弄错时,您将不会注意到任何事情。直到有一天,当 Blazor Wasm 最终获得真正的线程时,您的代码才会失败。就像您的计时器实验一样。

What is "Blazor's synchronization context"?

在 .net 中,同步上下文决定等待(之后)会发生什么。不同的平台有不同的设置,Blazor synccontext 很像 WinForms 和 WPF。主要是默认是.ConfigureAwait(true): resume on the same thread.

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

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

I also sometimes see InvokeAsync() called without await

那行得通。它可能比其他选项更好:将调用方法(如 Timer 的 OnTick)设为 async void。所以请从同步代码路径使用它。