I know that calling the StateHasChanged()
method notifies the component that the state has changed and so 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's different from StateHasChanged()
and where one should be chosen 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?!
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 get this Invoke pattern wrong you won't notice anything.
StateHasChanged notifies the component that its state has changed. When applicable, calling StateHasChanged causes the component to be rerendered. StateHasChanged is called automatically for EventCallback methods. For more information on event callbacks, see ASP.NET Core Blazor event handling.
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 get this Invoke pattern wrong you won't notice anything. Until one day, when Blazor Wasm finally gets real threads, your code will fail. As is the case with your Timer experiment.
What is "Blazor's synchronization context"?
In .net a synchronization 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, but not for the toplevel methods.
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 will work. It probably is better than the other option: making the calling method (like the Timer's OnTick) an async void
. So do use this from a synchronous code path.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With