问题
I know that calling the StateHasChanged()
method notifies the component that the state has changed and that it should re-render.
However, I also see calls to await InvokeAsync(StateHasChanged)
or await InvokeAsync(() => StateHasChanged())
in other people's code, but I don't quite understand how it compares to StateHasChanged()
and when should one be favored over the other, and why.
The only information I could find was this part of the Blazor docs, it says:
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.
I don't quite get this. It just says "...which dispatches to Blazor's synchronization context", but I'm not quite satisfied with that! What is "Blazor's synchronization context"?
I have tried calling StateHasChanged()
- instead of InvokeAsync(StateHasChanged)
- in a Timer
's Elapsed
event, and it works as expected, without any issues. Should I be calling await InvokeAsync(StateHasChanged)
instead?! And if so, why exactly? I feel like there's probably some important nuance here that I'm unaware of.
I've also seen calls like InvokeAsync(() => InvokeAsync(Something))
, again, why?
Plus, I also sometimes see InvokeAsync()
called without await
, what the deal with that?!
回答1:
I have tried calling StateHasChanged() - instead of InvokeAsync(StateHasChanged) - in a Timer's Elapsed event, and it works as expected
That must have been on WebAssembly. When you try that on Blazor Serverside I would expect an exception. StateHasChanged() checks if it runs on the right thread.
The core issue is that the rendering and calling StateHasChanged both have to happen on the main (UI) thread. The shadow copy of the DOM is not thread-safe.
The main Blazor life-cycle events (OnInit, AfterRender, ButtonClick) are all executed on that special thread so in the rare case that you need StateHasChanged() there it can be called without InvokeAsync().
A Timer is different, it is an "external event" so you can't be sure it will execute on the correct thread. InvokeAsync() delegates the work to Blazor's SynchronizationContext that will ensure it does run on the main thread.
But Blazor WebAssembly only has 1 thread so for the time being external events always run on the main thread too. That means that when you do this Invoke pattern wrong you won't notice anything. Until one day, when Blazor Wasm finally gets real threads, your code will fail. As will your Timer experiment.
What is "Blazor's synchronization context"?
In .net a sync context determines what happens with (after) await. Different platforms have different settings, the Blazor synccontext is a lot like that of WinForms and WPF. Mainly, the default is .ConfigureAwait(true)
: resume on the same thread.
I sometimes see .ConfigureAwait(false)
in toplevel Blazor Wasm code. That too will blow up when we get real threads there. It is fine to use in services called from blazor.
And finally, await InvokeAsync(StateHasChanged)
or await InvokeAsync(() => StateHasChanged()
is just about lambda's in C#, nothing to do with Blazor. The first short form is a little more efficient.
I also sometimes see
InvokeAsync()
called withoutawait
That is never a good idea, although it will usually work. It is better than making the calling method (like the Timer's OnTick) an async void
, so use this in synchronous code.
来源:https://stackoverflow.com/questions/65230621/statehaschanged-vs-invokeasyncstatehaschanged-in-blazor