refactor(store): 添加调试断言确保线程安全

- 在 CreateMiddlewareSnapshotCore 方法中添加锁持有检查
- 在 CreateReducerSnapshotCore 方法中添加锁持有检查
- 确保并发安全性避免竞态条件
- 导入 System.Diagnostics 命名空间支持断言功能
This commit is contained in:
GeWuYou 2026-03-24 19:39:55 +08:00
parent 91246ff482
commit bd29475748

View File

@ -157,6 +157,48 @@ public sealed class Store<TState> : IStore<TState>, IStoreDiagnostics<TState>
} }
} }
/// <summary>
/// 获取最近一次分发的 action 类型。
/// </summary>
public Type? LastActionType
{
get
{
lock (_lock)
{
return _lastActionType;
}
}
}
/// <summary>
/// 获取最近一次真正改变状态的时间戳。
/// </summary>
public DateTimeOffset? LastStateChangedAt
{
get
{
lock (_lock)
{
return _lastStateChangedAt;
}
}
}
/// <summary>
/// 获取当前历史快照列表。
/// </summary>
public IReadOnlyList<StoreHistoryEntry<TState>> HistoryEntries
{
get
{
lock (_lock)
{
return _history.Count == 0 ? Array.Empty<StoreHistoryEntry<TState>>() : _history.ToArray();
}
}
}
/// <summary> /// <summary>
/// 获取当前状态快照。 /// 获取当前状态快照。
/// </summary> /// </summary>
@ -278,54 +320,6 @@ public sealed class Store<TState> : IStore<TState>, IStoreDiagnostics<TState>
NotifyListeners(listenersSnapshot, notificationState); NotifyListeners(listenersSnapshot, notificationState);
} }
/// <summary>
/// 将多个状态操作合并到一个批处理中执行。
/// 批处理内的状态变化会立即提交,但通知会在最外层批处理结束后折叠为一次最终回放。
/// </summary>
/// <param name="batchAction">批处理主体。</param>
/// <exception cref="ArgumentNullException">当 <paramref name="batchAction"/> 为 <see langword="null"/> 时抛出。</exception>
public void RunInBatch(Action batchAction)
{
ArgumentNullException.ThrowIfNull(batchAction);
lock (_lock)
{
_batchDepth++;
}
Action<TState>[] listenersSnapshot = Array.Empty<Action<TState>>();
TState notificationState = default!;
try
{
batchAction();
}
finally
{
lock (_lock)
{
if (_batchDepth == 0)
{
throw new InvalidOperationException("Batch depth is already zero.");
}
_batchDepth--;
if (_batchDepth == 0 && _hasPendingBatchNotification)
{
notificationState = _pendingBatchState;
_pendingBatchState = default!;
_hasPendingBatchNotification = false;
listenersSnapshot = SnapshotListenersForNotification(notificationState);
}
}
}
if (listenersSnapshot.Length > 0)
{
NotifyListeners(listenersSnapshot, notificationState);
}
}
/// <summary> /// <summary>
/// 将当前状态回退到上一个历史点。 /// 将当前状态回退到上一个历史点。
/// </summary> /// </summary>
@ -375,6 +369,134 @@ public sealed class Store<TState> : IStore<TState>, IStoreDiagnostics<TState>
} }
} }
/// <summary>
/// 获取当前订阅者数量。
/// </summary>
public int SubscriberCount
{
get
{
lock (_lock)
{
return _listeners.Count;
}
}
}
/// <summary>
/// 获取最近一次分发记录。
/// </summary>
public StoreDispatchRecord<TState>? LastDispatchRecord
{
get
{
lock (_lock)
{
return _lastDispatchRecord;
}
}
}
/// <summary>
/// 获取当前 Store 使用的 action 匹配策略。
/// </summary>
public StoreActionMatchingMode ActionMatchingMode => _actionMatchingMode;
/// <summary>
/// 获取历史缓冲区容量。
/// </summary>
public int HistoryCapacity => _historyCapacity;
/// <summary>
/// 获取当前可见历史记录数量。
/// </summary>
public int HistoryCount
{
get
{
lock (_lock)
{
return _history.Count;
}
}
}
/// <summary>
/// 获取当前状态在历史缓冲区中的索引。
/// </summary>
public int HistoryIndex
{
get
{
lock (_lock)
{
return _historyIndex;
}
}
}
/// <summary>
/// 获取当前是否处于批处理阶段。
/// </summary>
public bool IsBatching
{
get
{
lock (_lock)
{
return _batchDepth > 0;
}
}
}
/// <summary>
/// 将多个状态操作合并到一个批处理中执行。
/// 批处理内的状态变化会立即提交,但通知会在最外层批处理结束后折叠为一次最终回放。
/// </summary>
/// <param name="batchAction">批处理主体。</param>
/// <exception cref="ArgumentNullException">当 <paramref name="batchAction"/> 为 <see langword="null"/> 时抛出。</exception>
public void RunInBatch(Action batchAction)
{
ArgumentNullException.ThrowIfNull(batchAction);
lock (_lock)
{
_batchDepth++;
}
Action<TState>[] listenersSnapshot = Array.Empty<Action<TState>>();
TState notificationState = default!;
try
{
batchAction();
}
finally
{
lock (_lock)
{
if (_batchDepth == 0)
{
throw new InvalidOperationException("Batch depth is already zero.");
}
_batchDepth--;
if (_batchDepth == 0 && _hasPendingBatchNotification)
{
notificationState = _pendingBatchState;
_pendingBatchState = default!;
_hasPendingBatchNotification = false;
listenersSnapshot = SnapshotListenersForNotification(notificationState);
}
}
}
if (listenersSnapshot.Length > 0)
{
NotifyListeners(listenersSnapshot, notificationState);
}
}
/// <summary> /// <summary>
/// 订阅状态变化通知。 /// 订阅状态变化通知。
/// </summary> /// </summary>
@ -476,128 +598,6 @@ public sealed class Store<TState> : IStore<TState>, IStoreDiagnostics<TState>
} }
} }
/// <summary>
/// 获取最近一次分发的 action 类型。
/// </summary>
public Type? LastActionType
{
get
{
lock (_lock)
{
return _lastActionType;
}
}
}
/// <summary>
/// 获取最近一次真正改变状态的时间戳。
/// </summary>
public DateTimeOffset? LastStateChangedAt
{
get
{
lock (_lock)
{
return _lastStateChangedAt;
}
}
}
/// <summary>
/// 获取当前订阅者数量。
/// </summary>
public int SubscriberCount
{
get
{
lock (_lock)
{
return _listeners.Count;
}
}
}
/// <summary>
/// 获取最近一次分发记录。
/// </summary>
public StoreDispatchRecord<TState>? LastDispatchRecord
{
get
{
lock (_lock)
{
return _lastDispatchRecord;
}
}
}
/// <summary>
/// 获取当前 Store 使用的 action 匹配策略。
/// </summary>
public StoreActionMatchingMode ActionMatchingMode => _actionMatchingMode;
/// <summary>
/// 获取历史缓冲区容量。
/// </summary>
public int HistoryCapacity => _historyCapacity;
/// <summary>
/// 获取当前可见历史记录数量。
/// </summary>
public int HistoryCount
{
get
{
lock (_lock)
{
return _history.Count;
}
}
}
/// <summary>
/// 获取当前状态在历史缓冲区中的索引。
/// </summary>
public int HistoryIndex
{
get
{
lock (_lock)
{
return _historyIndex;
}
}
}
/// <summary>
/// 获取当前历史快照列表。
/// </summary>
public IReadOnlyList<StoreHistoryEntry<TState>> HistoryEntries
{
get
{
lock (_lock)
{
return _history.Count == 0 ? Array.Empty<StoreHistoryEntry<TState>>() : _history.ToArray();
}
}
}
/// <summary>
/// 获取当前是否处于批处理阶段。
/// </summary>
public bool IsBatching
{
get
{
lock (_lock)
{
return _batchDepth > 0;
}
}
}
/// <summary> /// <summary>
/// 创建一个用于当前状态类型的 Store 构建器。 /// 创建一个用于当前状态类型的 Store 构建器。
/// </summary> /// </summary>
@ -915,6 +915,10 @@ public sealed class Store<TState> : IStore<TState>, IStoreDiagnostics<TState>
/// <returns>当前中间件链的快照;若未注册则返回空数组。</returns> /// <returns>当前中间件链的快照;若未注册则返回空数组。</returns>
private IStoreMiddleware<TState>[] CreateMiddlewareSnapshotCore() private IStoreMiddleware<TState>[] CreateMiddlewareSnapshotCore()
{ {
Debug.Assert(
Monitor.IsEntered(_lock),
"Caller must hold _lock before invoking CreateMiddlewareSnapshotCore to avoid concurrency bugs.");
if (_middlewares.Count == 0) if (_middlewares.Count == 0)
{ {
return Array.Empty<IStoreMiddleware<TState>>(); return Array.Empty<IStoreMiddleware<TState>>();
@ -937,6 +941,10 @@ public sealed class Store<TState> : IStore<TState>, IStoreDiagnostics<TState>
/// <returns>对应 action 类型的 reducer 快照;若未注册则返回空数组。</returns> /// <returns>对应 action 类型的 reducer 快照;若未注册则返回空数组。</returns>
private IStoreReducerAdapter[] CreateReducerSnapshotCore(Type actionType) private IStoreReducerAdapter[] CreateReducerSnapshotCore(Type actionType)
{ {
Debug.Assert(
Monitor.IsEntered(_lock),
"Caller must hold _lock before invoking CreateReducerSnapshotCore to avoid concurrency bugs.");
if (_actionMatchingMode == StoreActionMatchingMode.ExactTypeOnly) if (_actionMatchingMode == StoreActionMatchingMode.ExactTypeOnly)
{ {
if (!_reducers.TryGetValue(actionType, out var exactReducers) || exactReducers.Count == 0) if (!_reducers.TryGetValue(actionType, out var exactReducers) || exactReducers.Count == 0)