3

I've recently inherited a Blazor Webassembly application, however have minimal experience with dotnet or Blazor.

Some of the components use await Task.Run(StateHasChanged) rather than await InvokeAsync(StateHasChanged) and I'm wondering if this is intentional.

I ask as await Task.Run(StateHasChanged); gives me the following exception when attempting to render the component using bUnit:

System.InvalidOperationException The current thread is not associated with the Dispatcher. Use InvokeAsync() to switch execution to the Dispatcher when triggering rendering or component state.

Changing this to await InvokeAsync(StateHasChanged); allows the component to be rendered in bUnit. However, as far as I can tell, the component functions identically when using the application for either await Task.Run(StateHasChanged) or await InvokeAsync(StateHasChanged).

What is the difference between these two approaches to invoking StateHasChanged?

Sam Carswell
  • 129
  • 12
  • 4
    This post explains the difference in general: https://stackoverflow.com/questions/38739403/await-task-run-vs-await/ I'd imagine that the StateHasChanged stuff follows the same explaination "Task.Run may post the operation to be processed at a different thread. That's the only difference." – gunr2171 Nov 26 '21 at 00:42
  • a DevOps engineer inheriting a Blazor project. All the best to you! – Ayudh Nov 26 '21 at 02:24
  • InvokeAsync needs to be called when UI refresh are needed from actions on a non-UI event, this will allow to send the StateHasChanged to the UI Thread. When an event is UI based, this is not needed. Task.Run creates a new context, it is not on the UI Thread. (Asp.net Core Doc on using StateHasChanged) https://learn.microsoft.com/en-us/aspnet/core/blazor/components/rendering?view=aspnetcore-6.0#when-to-call-statehaschanged – Shuryno Nov 26 '21 at 14:40
  • By threads, I am not sure I use the right term, its more like context, as with Blazor Wasm, it is not clear it can handle multiple threads yet. Webassembly seems to support it, but not all browser do, or did not a long time ago. So using a Task.Run can and will block the main thread if multiple threads are not yet supported. – Shuryno Nov 26 '21 at 14:55
  • @Shuryno: that 1st comment would have made an answer... And Task.Run() won't block, it just gets executed on the main Thread. – H H Nov 29 '21 at 09:10
  • @Henk Holterman, first link explains Task.Run, but not InvokeAsync... It provide a nice info though. As for running a Task on main thread, If you run a long task expecting it to run in parallel, but in fact runs on your main thread, what are the effects of it? – Shuryno Nov 29 '21 at 12:19
  • It would block the UI. Not recommended. – H H Nov 29 '21 at 12:35

1 Answers1

2

as far as I can tell, the component functions identically ...

That is correct. Task.Run(job) will run the job on the ThreadPool. In WebAssembly however there are no extra threads and the main (only) thread will have to run this job sooner or later.

In Blazor Server you do have Threads. Task.Run() will work there, but StateHasChanged() has to run on the main Thread. That means that

 await Task.Run(StateHasChanged)   // bug!  Don't do this.

definitely is a bug, everywhere. It just goes unnoticed on WebAssembly for the time being. Until the day that Blazor Wasm also gets threads, then it will throw.

So bUnit is right, fix your code.

Do note that in 'normal' lifecycle events (like OnInitialized[Async], OnClick, OnSubmit etc) you don't have to use InvokeAsync() at all. I normally use just

  StateHasChanged();

and in in external events (eg a Timer) or Threaded code (on the Server)

  await InvokeAsync(StateHasChanged);
H H
  • 263,252
  • 30
  • 330
  • 514
  • I think the explanation for him getting that error now and the person who develop didn't might be because the browser @Sam Carswell is using supports Wasm multi-thread and it was develop on a browser that didn't? Can't see any other reason it was left there. – Shuryno Nov 29 '21 at 14:14
  • 1
    No, ther error is when running in bUnit and bUnit always runs server-side, hence the error. Blazor doesn't support Wasm Threads in any Browser (yet), afaik. – H H Nov 29 '21 at 14:27
  • Oh, ok, yeah I missed that bUnit reference. Good point! :) – Shuryno Nov 29 '21 at 14:34
  • @HenkHolterman Thanks for the detailed answer. I have discovered there is a slight difference between the two approaches (not sure if it's intentional or a bug), but I've explained the differing outcomes [here](https://stackoverflow.com/questions/70193854/blazor-statehaschanged-and-child-parameters-await-task-runstatehaschanged) – Sam Carswell Dec 02 '21 at 04:20