为什么 Blazor 值更改后没有立即渲染?

问题描述 投票:0回答:1

在 WinForms .NET 8 表单中使用 BlazorWebView 控件(简介),我确实显示了这个“Counter.razor”文件:

<p><button @onclick="IncrementCount">Increment counter</button></p>
<p>@_counterValue</p>

@code
{
    private int _counterValue = 0;

    private void IncrementCount()
    {
        _counterValue++;
    }
}

正确的行为

上面的代码完美运行:

  1. 用户单击按钮。
  2. 变量递增。
  3. 页面显示新的变量值。

不正确的行为

现在我将

IncrementCount()
方法更改为:

private void IncrementCount()
{
    SynchronizationContext.Current!.Post(
        delegate
        {
            _counterValue++;
        }, 
        null);
}

现在它的行为是这样的:

  1. 用户单击按钮。
  2. 变量递增(现在为“1”)。
  3. 页面仍显示“0”。
  4. 用户再次单击按钮。
  5. 变量再次递增。 (现在是“2”)。
  6. 页面显示“1”。
  7. 等等。

即我总是有一个相差一的错误。

修复它

要解决此问题,我必须将代码更改为:

private void IncrementCount()
{
    SynchronizationContext.Current!.Post(
        delegate
        {
            _counterValue++;
            StateHasChanged();
        }, 
        null);
}

现在,添加对

StateHasChanged()
的调用后,它的行为符合预期:

  1. 用户单击按钮。
  2. 变量递增。
  3. 页面显示新的变量值。

背景

上面

SynchronizationContext
的用法是一个大型应用程序的最小示例,我必须通过这种机制调度调用(主要是因为我必须调用其他WinForms对话框)。

我确实想了解为什么需要调用“StateHasChanged”,因为我想避免它。

在我的实际应用程序中(不是上面的最小示例),这将强制进行大量重新渲染以适合应用程序。

我的问题

有人可以向我解释一下为什么我直接更改变量与通过

SynchronizationContext
更改变量的行为不同吗?

是否有更改可以使其工作而无需额外调用

StateHasChanged()


更新1

在我的现实世界应用程序中,我大致执行以下步骤:

  1. 用户单击 Blazor 按钮。
  2. 显示 WinForms 表单。
  3. 关闭表单后,我必须更新 Blazor 中的一些值。

所以这就是我在

SynchronizationContext.Current!.Post()
调用中执行代码的原因,如建议的 here

我还尝试将我的代码放入队列中并在

Application.Idle
事件处理程序中处理该队列,但这行为完全相同(相差一错误)。

c# winforms blazor maui-blazor blazorwebview
1个回答
1
投票

IncrementCounter
由渲染器的 UI 事件进程发布到 同步上下文。它在
callback
UI 处理程序中作为
ComponentBase
传递。

    Task IHandleEvent.HandleEventAsync(EventCallbackWorkItem callback, object? arg)
    {
        var task = callback.InvokeAsync(arg);
        var shouldAwaitTask = task.Status != TaskStatus.RanToCompletion &&
            task.Status != TaskStatus.Canceled;

        // After each event, we synchronously re-render (unless !ShouldRender())
        // This just saves the developer the trouble of putting "StateHasChanged();"
        // at the end of every event callback.
        StateHasChanged();

        return shouldAwaitTask ?
            CallStateHasChangedOnAsyncCompletion(task) :
            Task.CompletedTask;
    }

完整代码可以在这里找到:https://github.com/dotnet/aspnetcore/blob/63c8031b6a6af5009b3c5bb4291fcc4c32b06b10/src/Components/Components/src/ComponentBase.cs#L322

所以你正在做的就是发布这个

{
    _counterValue++;
} 

作为代码块进入IncrementCounter后面的

同步上下文
队列。
IncrementCounter
在执行代码之前完成所有渲染并改变
_counterValue

当直接更改时,突变会在 UI 处理程序中调用

StateHasChanged
之前发生。

问题:为什么要直接将代码发布到同步上下文

ComponentBase
具有内置函数
InvokeAsync
来调用必须在同步上下文上执行的任何代码,请参阅 - https://github.com/dotnet/aspnetcore/blob/63c8031b6a6af5009b3c5bb4291fcc4c32b06b10/src/Components/Components/ src/ComponentBase.cs#L178

注:

我现在从来没有编写过这个代码:

private void IncrementCount()
{ }

我总是这样做:

private Task IncrementCount()
{ 
    //work
    return Task.CompletedTask;
}

它可以防止这种情况发生:

private async void IncrementCount()
{ }
© www.soinside.com 2019 - 2024. All rights reserved.