Using a BlazorWebView control (introduction) inside a WinForms .NET 8 form, I do have this "Counter.razor" file being displayed:
<p><button @onclick="IncrementCount">Increment counter</button></p>
<p>@_counterValue</p>
@code
{
private int _counterValue = 0;
private void IncrementCount()
{
_counterValue++;
}
}
Correct behavior
The above code works perfectly:
- The user clicks the button.
- The variable is incremented.
- The page displays the new variable value.
Incorrect behavior
Now I change the IncrementCount() method to this:
private void IncrementCount()
{
SynchronizationContext.Current!.Post(
delegate
{
_counterValue++;
},
null);
}
Now it misbehaves like this:
- The user clicks the button.
- The variable is incremented (now being "1").
- The page still displays "0".
- The user clicks the button again.
- The variable is incremented again. (Now being "2").
- The page displays "1".
- and so on.
I.e. I always do have an off-by-one error.
Fixing it
To fix this, I do have to change my code to:
private void IncrementCount()
{
SynchronizationContext.Current!.Post(
delegate
{
_counterValue++;
StateHasChanged();
},
null);
}
Now, after adding a call to StateHasChanged(), it behaves as intended:
- The user clicks the button.
- The variable is incremented.
- The page displays the new variable value.
Context
The above usage of SynchronizationContext is a minimal example of a larger application where I have to dispatch calls through this mechanism (primarily because I have to call other WinForms dialogs).
I do want to understand why the call to "StateHasChanged" is required as I want to avoid it.
In my real application (not the above minimal example) this would force to much re-rendering to be suitable for the application.
My question
Could someone please explain to me why the behavior is different whether I change the variable directly vs. changing it through SynchronizationContext?
Is there a change to make this working without the extra call to StateHasChanged()?
Update 1
In my real world application I'm doing roughly these steps:
- User clicks a Blazor button.
- A WinForms form is shown.
- After closing the form, I have to update some values in Blazor.
So this is the reason I am doing my code inside a SynchronizationContext.Current!.Post() call, as recommended here.
I've also tried to place my code into a queue and process this queue in an Application.Idle event handler of my WinForms app, but this behaves just the same (the off-by-one error).


IncrementCounteris posted to the Synchronization Context by the Renderer's UI Event process. It's passed ascallbackinside theComponentBaseUI handler.The full code can be found here: https://github.com/dotnet/aspnetcore/blob/63c8031b6a6af5009b3c5bb4291fcc4c32b06b10/src/Components/Components/src/ComponentBase.cs#L322
So what you're doing is posting this
as a block of code into the Synchronization Context queue behind
IncrementCounter.IncrementCounterdoes all the rendering before your code is executed and mutates the_counterValue.When it's changed directly the mutation takes place before
StateHasChangedis called in the UI handler.Question: Why do you want to post code directly to the Synchronization Context?
ComponentBasehas the inbuilt functionInvokeAsyncto call any code that must be executed on the Synchronization Context see - https://github.com/dotnet/aspnetcore/blob/63c8031b6a6af5009b3c5bb4291fcc4c32b06b10/src/Components/Components/src/ComponentBase.cs#L178Note [personal]:
I now always change:
To this:
It prevents Visual Studio automatically doing this when you type
await:Update:
Based on your update you need to call
StateHasChangedin the posted [anonymous] method.Note that you can disable all automatic calls to
StateHasChangedin a component like this:You then only call
StateHasChangedwhen you need to.