1
votes

Using the default project that loads with Visual Studio 2019 when you create a new blazor server-side project in .NetCore 3.1, consider the following example (a slight change to their own counter example page):

<button class="btn btn-primary" @onclick="SingleMethodUpdate">Click Me</button>
<label>Update from same method: @result</label>
<button class="btn btn-primary" @onclick="NestedMethodUpdate">Click Me</button>
<label>Update from nested method: @nestedResult</label>

And the code-behind to look like this:

int result = 0;
int nestedResult = 0;
int nestedCounter = 0;

protected async Task SingleMethodUpdate()
{
    result++;
}

protected async Task NestedMethodUpdate()
{
    await NestedUpdate(++nestedCounter);
    await Task.Delay(1000);
    await NestedUpdate(++nestedCounter);
    await Task.Delay(1000);
    await NestedUpdate(++nestedCounter);
}

protected Task NestedUpdate(int update)
{
    nestedResult = update;
    return Task.FromResult(0);
}

The first button and label is simple, press a button and the count on the UI updates as soon as you press it as expected. However, the second button is more complicated.

It is the middle await NestedUpdate(++nestedCounter) call that doesn't get reflected on the UI. The first call and the last call (first and last nested method call) are both reflected in real-time on the UI, so what you see is:

Update from nested method: 1
*2 seconds go by
Update from nested method: 3

I realize this is because of the caveats with SynchronizationContext but I am more curious as to how I would go about capturing and maintaining that context such that every awaited method that is called within my button press event uses that same context so that the UI will update in real-time as the code executes. Is there a nice/pretty way to do this?

1
ConfigureAwait(true) maybe ?agua from mars
@HenkHolterman how do you mean?Giardino

1 Answers

1
votes

I realize this is because of the caveats with SynchronizationContext

No, not really. All this code should run async but sequentially on the main thread.
Nothing wrong or in need of fixing with the SyncContext here.

What you see is due to the StateHasChanged() logic, which really is about a State, presumably just a boolean flag.

The Blazor framework will set this flag before and after it calls your eventhandler but in the middle you will have to do so your self.

A quick fix and explanation:

protected async Task NestedMethodUpdate()
{
    // the flag is pre-set here
    await NestedUpdate(++nestedCounter);
    // the thread is available and Blazor will render '1' and reset the flag
    await Task.Delay(1000);  

    await NestedUpdate(++nestedCounter);
    // the thread is available but the flag is down: the '2' is not shown
    await Task.Delay(1000);

    await NestedUpdate(++nestedCounter);
    StatehasChanged();  // <<-- add this
    // the thread is available and the flag is up: the '3' is shown
    await Task.Delay(1000);

    // you can add more steps
    await NestedUpdate(++nestedCounter);
    StatehasChanged();  // <<-- set the flag
    await Task.Delay(1000);  // show '4'

    // this result will show '5' after the method has completed
    await NestedUpdate(++nestedCounter);
}