You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Blazor Server双方法完成后执行代码的实现与疑问

Blazor Server 双方法完成后执行特定代码的实现疑问

场景描述

需要在OnInitializedAsync()和OnAfterRenderAsync()两个方法中,等完成较晚的那个执行结束后,运行依赖可调用JavaScript及完整数据库页面模型的特定代码。已实现DoubleFinish类处理该逻辑,同时有两个并发相关的疑问。

实现代码

DoubleFinish 类

public class DoubleFinish
{
    private volatile bool _firstFinished = false;
    private volatile bool _secondFinished = false;
    private volatile bool _alreadyReturnedTrue = false;

    /// <summary>
    /// 第一个方法完成时调用。若第二个方法也已完成,则返回true,此时可运行依赖双方法完成的代码。
    /// </summary>
    public bool TryFirstFinished
    {
        get
        {
            lock (this)
            {
                _firstFinished = true;
                if (_alreadyReturnedTrue || ! _secondFinished)
                    return false;
                _alreadyReturnedTrue = true;
                return true;
            }
        }
    }

    /// <summary>
    /// 第二个方法完成时调用。若第一个方法也已完成,则返回true,此时可运行依赖双方法完成的代码。
    /// </summary>
    public bool TrySecondFinished
    {
        get
        {
            lock (this)
            {
                _secondFinished = true;
                if (_alreadyReturnedTrue || ! _firstFinished)
                    return false;
                _alreadyReturnedTrue = true;
                return true;
            }
        }
    }
}

组件中使用方式

async Task OnInitializedAsync() {
    // ... 大量数据库操作
    if (DoubleFinish.TryFirstFinished)
        await OnAfterInitializeAndRenderAsync();
}

async Task OnAfterRenderAsync(bool firstRender) {
    if (!firstRender)
        return;
    if (DoubleFinish.TrySecondFinished)
        await OnAfterInitializeAndRenderAsync();
}

疑问

  1. DoubleFinish类中的bool变量是否需要声明为volatile?
  2. 是否存在可替代lock的原子调用,用于检查/设置bool值?

更新说明:一个关键约束是OnInitializedAsync()在某些配置下会被调用两次,因此无法使用计数器,必须单独跟踪每个方法是否完成。


回答

关于 volatile 的必要性

不需要。所有对这些bool变量的读写都被包裹在lock代码块中,lock本身已经保证了内存可见性:进入锁的线程会刷新本地缓存,退出锁时会将修改同步到主内存,其他线程进入同一锁时能获取到最新值。volatile在这里属于多余配置,甚至可能造成误解,因为锁已经提供了完整的内存屏障。

替代 lock 的原子操作方案

可以通过Interlocked类或TaskCompletionSource实现无锁/异步式的原子逻辑,以下是两种可行思路:

思路1:位标记+Interlocked原子操作

用整数的不同位来标记三个状态,通过Interlocked.CompareExchange实现原子性的检查与更新:

public class DoubleFinish
{
    private int _state = 0;
    private const int FirstFinished = 1;
    private const int SecondFinished = 2;
    private const int AlreadyTriggered = 4;

    public bool TryFirstFinished
    {
        get
        {
            while (true)
            {
                var currentState = _state;
                // 已触发过直接返回false
                if ((currentState & AlreadyTriggered) != 0)
                    return false;
                
                var newState = currentState | FirstFinished;
                // 第二个方法已完成,尝试标记为已触发
                if ((currentState & SecondFinished) != 0)
                {
                    newState |= AlreadyTriggered;
                    if (Interlocked.CompareExchange(ref _state, newState, currentState) == currentState)
                        return true;
                }
                // 仅标记第一个方法完成
                else
                {
                    if (Interlocked.CompareExchange(ref _state, newState, currentState) == currentState)
                        return false;
                }
                // 更新失败则循环重试
            }
        }
    }

    public bool TrySecondFinished
    {
        get
        {
            while (true)
            {
                var currentState = _state;
                if ((currentState & AlreadyTriggered) != 0)
                    return false;
                
                var newState = currentState | SecondFinished;
                if ((currentState & FirstFinished) != 0)
                {
                    newState |= AlreadyTriggered;
                    if (Interlocked.CompareExchange(ref _state, newState, currentState) == currentState)
                        return true;
                }
                else
                {
                    if (Interlocked.CompareExchange(ref _state, newState, currentState) == currentState)
                        return false;
                }
            }
        }
    }
}

这种方式完全依赖原子操作避免锁开销,但代码复杂度更高,需要处理循环重试的情况。

思路2:TaskCompletionSource异步等待

贴合Blazor的异步场景,用两个TaskCompletionSource跟踪方法完成状态,通过Task.WhenAll等待双方法完成,再确保回调只执行一次:

public class DoubleFinish
{
    private readonly TaskCompletionSource<bool> _firstTcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
    private readonly TaskCompletionSource<bool> _secondTcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
    private int _triggered = 0;

    public void MarkFirstFinished()
    {
        _firstTcs.TrySetResult(true);
    }

    public void MarkSecondFinished()
    {
        _secondTcs.TrySetResult(true);
    }

    public async Task<bool> WaitForBothAsync()
    {
        await Task.WhenAll(_firstTcs.Task, _secondTcs.Task);
        // 用原子操作确保仅执行一次
        return Interlocked.Exchange(ref _triggered, 1) == 0;
    }
}

组件中使用示例:

private DoubleFinish _doubleFinish = new DoubleFinish();

protected override async Task OnInitializedAsync()
{
    // ... 数据库操作
    _doubleFinish.MarkFirstFinished();
    if (await _doubleFinish.WaitForBothAsync())
        await OnAfterInitializeAndRenderAsync();
}

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    if (!firstRender) return;
    _doubleFinish.MarkSecondFinished();
    if (await _doubleFinish.WaitForBothAsync())
        await OnAfterInitializeAndRenderAsync();
}

这种方式更符合异步编程习惯,代码可读性更强,也能自然处理OnInitializedAsync被多次调用的情况。

总结

  • volatile无需添加,lock已保证内存可见性;
  • 无锁方案可选Interlocked原子操作,但代码复杂度高;更推荐TaskCompletionSource结合异步等待的实现,贴合Blazor场景。

内容的提问来源于stack exchange,提问作者David Thielen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 20:13:12