66

I know that calling the StateHasChanged() method notifies the component that the state has changed and so it should re-render.

However, I also sometimes see things like await InvokeAsync(StateHasChanged) or await InvokeAsync(() => StateHasChanged()) in other people's code, but I'm not sure how they're different from StateHasChanged() and where one of them should be preferred over the others, and why.

The only information I could find was this part of the Blazor docs, stating that:

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 understand this. It just says "...which dispatches to Blazor's synchronization context", I'm not satisfied with that, what exactly 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?!

Too many questions sorry! :D

Arad Alvand
  • 8,607
  • 10
  • 51
  • 71

1 Answers1

61

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 virtual 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 without await

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.

H H
  • 263,252
  • 30
  • 330
  • 514
  • Thank you. By the way, about your statement `And finally, await InvokeAsync(StateHasChanged or await InvokeAsync(() => StateHasChanged() is just about lambda's in C#, nothing to do with Blazor.` But what I meant was I actually see an `InvokeAsync` call within another one, as in [here](https://github.com/dotnet/aspnetcore/blob/321db9d99f84cf7a67d453384292d9339de748d1/src/Components/test/testassets/BasicTestApp/DispatchingComponent.razor#L42) (Line 42) – Arad Alvand Dec 10 '20 at 08:43
  • Hmm, that is a Test, not sure what it is for. It is not 'real code'. – H H Dec 10 '20 at 08:48
  • Oh, okay, good to know. Thanks! One more thing: You stated `...Until one day, when Blazor Wasm finally gets real threads, your code will fail`, is that actually coming? Or is Blazor WebAssembly expected to always remain single-threaded? Which would in turn mean that `InvokeAsync` would always be effectively irrelevant in Blazor WebAssembly, and only relevant in Blazor Sever-side. – Arad Alvand Dec 10 '20 at 08:55
  • 2
    It's under discussion I think : https://github.com/dotnet/aspnetcore/discussions/22885 – H H Dec 10 '20 at 08:57
  • Ok, so it's not decided yet. Thanks! – Arad Alvand Dec 10 '20 at 09:03
  • The Test code seems to check this, including the error detection for server-side. It uses various ways to simulate an 'external' event from an internal buttonclick. – H H Dec 10 '20 at 09:16
  • 2
    @HenkHolterman If that sometimes StateHasChanged() causes error, shouldn't I always use await InvokeAsync(StateHasChanged)? Such as a brainless option. If not, why? – UserLuke Aug 27 '21 at 13:34
  • (Late reply:) No, I wouldn't use it always. It adds a tiny bit of overhead but more important: it should almost never be needed. I'd like to attract attention to those special cases where it is necessary. An Invoke() is a yellow flag. – H H Feb 03 '23 at 14:27
  • Btw, it also applies to Blazor Hybrid with WPF. WPF requires the UI update is done in Main thread. If the `StatusHasChanged()` is called on a different thread, it has to be wrapped with `InvokeAsync(StatusHasChanged)` instead. – aDisplayName May 09 '23 at 15:47