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(); }
疑问
DoubleFinish类中的bool变量是否需要声明为volatile?- 是否存在可替代
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
相关产品推荐
相关产品推荐

