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 的同步上下文”?
我尝试在 Timer
的 Elapsed
事件中调用 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
。所以请从同步代码路径使用它。
我知道调用 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 的同步上下文”?
我尝试在 Timer
的 Elapsed
事件中调用 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 withoutawait
那行得通。它可能比其他选项更好:将调用方法(如 Timer 的 OnTick)设为 async void
。所以请从同步代码路径使用它。