Is there a better approach than slamming await Task.Yield();
after isBusy = true;
, or this is the way?
You aren't Slamming anything. You're setting where the first yield happens, and thus the intermediate "I'm Busy" render occurs.
Here's a detailed explanation of what's happening.
Some background information:
All BLazor UI code [lifecycle methods, UI events an callbacks] is executed on a Synchronisation Context [SC from now on], which enforces a single logical thread of execution.
In ComponentBase
inherited components, UI events are handled by the registered IHandleEvent.HandleEventAsync
. This is a simplified version.
async Task IHandleEvent.HandleEventAsync(EventCallbackWorkItem item, object? obj)
{
var uiTask = item.InvokeAsync(obj);
var isCompleted = uiTask.IsCompleted || uiTask.IsCanceled;
if (!isCompleted)
{
this.StateHasChanged();
await uiTask;
}
this.StateHasChanged();
}
When the button gets clicked and the renderer handles the event.
IHandleEvent.HandleEventAsync
is invoked and passed the registered handler [DoSomethingAsync].
It invokes DoSomethingAsync
var uiTask = item.InvokeAsync(obj);
The first two lines of DoSomethingAsync
are a synchronous block so run and block the SC.
isBusy = true;
Thread.Sleep(2000); // simulate sync work
After 2000 ms, we reach the await:
await Task.Delay(2000); // simulate async work
This creates a continuation on the SC, and yields.
This code block in IHandleEvent.HandleEventAsync
now runs. It queues a render request on the Renderer's queue and yields [if uiTask han't completed]. The Renderer gets control of the SC, services it's queue and renders the component.
var isCompleted = uiTask.IsCompleted || uiTask.IsCanceled;
if (!isCompleted)
{
this.StateHasChanged();
await uiTask;
After 2000 ms:
await Task.Delay(2000); // simulate async work
completes and the contination runs to completion.
isBusy = false;
In IHandleEvent.HandleEventAsync
uiTask
is now completes so:
this.StateHasChanged();
runs and queues a render. The SC is free so the renderer services it's queue and renders the component.
If you're running on Blazor Server, you can offload your sync task to the threadpool. If not, then you're stuck with the SC.