8

Edit: for clarification, this is in a Blazor Server application

I'm confused about the correct usage of InvokeAsync and updating the UI. The documentation has several usages without really explaining the reason for awaiting or not awaiting. I have also seen some contradictory threads without much to backup their reasoning.

It seems wrong to make all methods async to await InvokeAsync(StateHasChanged) and I read somewhere that the reason InvokeAsync was introduced was to prevent the need for async code everywhere. But then what situations might I want to await it?

awaiting:

https://learn.microsoft.com/en-us/aspnet/core/blazor/components/?view=aspnetcore-5.0&viewFallbackFrom=aspnetcore-3.0#invoke-component-methods-externally-to-update-state

non-await discard:

https://learn.microsoft.com/en-us/aspnet/core/blazor/components/rendering?view=aspnetcore-5.0

Here are some examples of different usages that I have seen, if anyone could explain or share any links with information on some of the differences between them that'd be great (thanks!)

public void IncrementCounter()
{
    _counter++;
    InvokeAsync(StateHasChanged);
}
public void IncrementCounter()
{
    InvokeAsync(() => 
    {
        _counter++;
        StateHasChanged);
    }
}
public async Task IncrementCounter()
{
    _counter++;
    await InvokeAsync(StateHasChanged);
}
public async Task IncrementCounter()
{
    await InvokeAsync(() =>
    {
        _counter++;
        StateHasChanged();
    });
}
LeeOkeefe
  • 125
  • 1
  • 6
  • 1
    Why use `InvokeAsync` at all? `StateHasChanged` doesn't modify any UI nor does it block, it tells Blazor that a component has changed and Blazor should redraw it – Panagiotis Kanavos Jan 25 '21 at 09:19
  • In fact, even `StateHasChanged` may not be needed. In the Blazor tutorial [the counter doesn't use SaveChanges](https://dotnet.microsoft.com/learn/aspnet/blazor-tutorial/try) because Blazor itself detects that `counter` has changed – Panagiotis Kanavos Jan 25 '21 at 09:21
  • 2
    @Panagiotis Kanavos, I'm sorry but you're completely wrong. InvokeAsync is necessary in multi-threaded environment such as Blazor Server App. Currently it is not necessary in WebAssembly Blazor App, but it is recommended to be used because of future changes in WebAsembly becoming mutli-threaded as well. – enet Jan 25 '21 at 09:42
  • Yes this may have been a mistake of mine, I forgot to mention this is for Blazor Server. InvokeAsync is needed to execute on the main thread (UI thread). If you try and call StateHasChanged on another thread, it will throw an InvalidOperationException. Blazor WebAssembly is single threaded at the time of writing, so you could use it without but it may break in future. – LeeOkeefe Jan 25 '21 at 09:55
  • I believe Blazor Server has a `SynchronizationContext`, in which case I would recommend not using `InvokeAsync` at all. Use the SyncCtx instead. – Stephen Cleary Jan 25 '21 at 14:50
  • 1
    @StephenCleary - Blazor has its own InvokeAsync() especially for this purpose. It also has its own SynchronizationContext but that is not documented very well. – H H Jan 25 '21 at 15:36

1 Answers1

9

IncrementCounter (a ButtonClick handler) is the wrong thing to look at - it always runs on the SyncContext thread and can always use a plain StateHasChanged() without Invoke.

So lets look at a Timer event instead. The Threading.Timer class does not support async handlers so you run in a void Tick() { ... } on an unspecified thread.

You do need InvokeAsync(StateHasChanged) here. You could make the Tick method an async void just to await the InvokeAsync but that gives of the wrong signals. Using InvokeAsync without await is the lesser evil.

void Tick()  // possibly threaded event handler
{
   _counter++;
   InvokeAsync(StateHasChanged);  // fire-and-forget mode
}

But when you are in an async method and still need InvokeAsync, it is neater to await it, just because you can.

async Task SomeService()  
{
   _counter++;
   await InvokeAsync(StateHasChanged); 
}
H H
  • 263,252
  • 30
  • 330
  • 514