Blazor Server 的单线程模拟是否忽略 JavaScript 互操作?

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

今天我遇到了一个问题,该组件封装了 JS 库,可能会影响 Blazor 组件的绑定值。更新

Mask
参数的值将导致 JavaScript 代码运行多次。

[Parameter]
public string Value { get; set; }

[Parameter]
public EventCallback<string> ValueChanged { get; set; }

[Parameter]
public Mask Mask { get; set; }

private Mask _mask;
private bool _maskNeedsUpdate;

protected override void OnParametersSet()
{
    // ... Handle other parameters.

    if (_mask != Mask)
    {
        _mask = Mask;
        _maskNeedsUpdate = true;
    }
}

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    // ... initialize JS mask on first render.

    if (_maskNeedsUpdate)
    {
        var valueAfterUpdate = await Js.InvokeAsync<string>("updateMask", _mask);
        if (valueAfterUpdate != Value)
        {
            await ValueChanged.InvokeAsync(valueAfterUpdate);
        }

        _maskNeedsUpdate = false; // <-- This causes problems.
    }
}

几乎就好像

ValueChanged.InvokeAsync()
调用触发了并行重新渲染,这会导致第二个 JS 互操作调用排队,因为标志仍然是
true
。但我对此表示怀疑,因为它在下一行实际上被设置为 false (同时,所有
EventCallback.InvokeAsync()
似乎所做的就是本质上在父组件中调用
StateHasChanged()
并调用其回调
)。

更改代码以设置标志在调用JS之前似乎已经解决了问题。

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    // ... initialize JS mask on first render.

    if (_maskNeedsUpdate)
    {
        _maskNeedsUpdate = false; // <-- Toggle flag before awaiting JS.

        var valueAfterUpdate = await Js.InvokeAsync<string>("updateMask", _mask);
        if (valueAfterUpdate != Value)
        {
            await ValueChanged.InvokeAsync(valueAfterUpdate);
        }
    }
}

这个修复有点让我困惑,因为 根据文档:

Blazor 使用同步上下文 (SynchronizationContext) 来强制执行单个逻辑线程。 Blazor 引发的组件的生命周期方法和事件回调在同步上下文上执行。

Blazor 的服务器端同步上下文尝试模拟单线程环境,以便它与浏览器中的单线程 WebAssembly 模型紧密匹配。此仿真仅适用于单个电路,这意味着两个不同的电路可以并行运行。在电路内的任何给定时间点,工作都在一个线程上执行,这会产生单个逻辑线程的印象。同一电路中不会同时执行两个操作。

我的理解是,单线程模拟不应允许我的 Blazor 组件在 JavaScript 仍在运行时“并行重新渲染”(并且,查看 Blazor 源代码,最终会等待

OnAfterRenderAsync()
)。但是,为什么在 JS 互操作调用之前将标志向上移动可以解决我的问题呢?即使父级重新渲染,单线程模拟不会阻止它并行重新渲染吗?

我想知道在设计调用 JavaScript 互操作的组件时是否应该注意竞争条件。

上面的示例是一个足够简单的修复,但我还有其他包装 JS 库的组件,这些组件具有更复杂的行为,其中 C# 和 JS 端都可以更改绑定参数,而“只是将标志移得更高”并不是如果组件可以并行重新渲染,那么它们将适用于其中一些。

javascript c# blazor interop
1个回答
0
投票
我的理解是单线程模拟不应该允许我的 Blazor 组件“并行重新渲染”

这是错误的假设。异步和多线程不一样。 Blazor 的渲染是异步(交错)的,是的,当您在任何地方执行等待时,可能会发生新的渲染。这一切都发生在同一个单线程上并不那么重要。

文档

中的注释对此进行了说明:

在渲染组件之前,生命周期事件中执行的异步操作可能无法完成。

所以你的原始代码确实有竞争条件,并且尽早设置标志确实是解决办法。

但是我还有其他封装 JS 库的组件,它们的行为要复杂得多

您必须具体情况具体分析。我认为除了不使用 AfterRender 事件来更新数据之外,没有通用的解决方案。应该可以将此逻辑移至 OnParamsSet。

© www.soinside.com 2019 - 2024. All rights reserved.