diff --git a/GFramework.Core.Tests/StateManagement/StoreTests.cs b/GFramework.Core.Tests/StateManagement/StoreTests.cs
index c9c4360..9101cab 100644
--- a/GFramework.Core.Tests/StateManagement/StoreTests.cs
+++ b/GFramework.Core.Tests/StateManagement/StoreTests.cs
@@ -1,5 +1,3 @@
-using GFramework.Core.Abstractions.Property;
-using GFramework.Core.Abstractions.StateManagement;
using GFramework.Core.Extensions;
using GFramework.Core.Property;
using GFramework.Core.StateManagement;
@@ -316,6 +314,40 @@ public class StoreTests
Assert.That(logs, Is.EqualTo(new[] { "builder:before", "builder:after", "builder:before", "builder:after" }));
}
+ ///
+ /// 测试长时间运行的 middleware 不会长时间占用状态锁,
+ /// 使读取状态和新增订阅仍能在 dispatch 进行期间完成。
+ ///
+ [Test]
+ public void Dispatch_Should_Not_Block_State_Read_Or_Subscribe_While_Middleware_Is_Running()
+ {
+ using var entered = new ManualResetEventSlim(false);
+ using var release = new ManualResetEventSlim(false);
+
+ var store = CreateStore();
+ store.UseMiddleware(new BlockingMiddleware(entered, release));
+
+ var dispatchTask = Task.Run(() => store.Dispatch(new IncrementAction(1)));
+
+ Assert.That(entered.Wait(TimeSpan.FromSeconds(2)), Is.True, "middleware 未按预期进入阻塞阶段");
+
+ var stateReadTask = Task.Run(() => store.State.Count);
+ Assert.That(stateReadTask.Wait(TimeSpan.FromMilliseconds(200)), Is.True, "State 读取被 dispatch 长时间阻塞");
+ Assert.That(stateReadTask.Result, Is.EqualTo(0), "middleware 执行期间应仍能读取到提交前的状态快照");
+
+ var subscribeTask = Task.Run(() =>
+ {
+ var unRegister = store.Subscribe(_ => { });
+ unRegister.UnRegister();
+ });
+ Assert.That(subscribeTask.Wait(TimeSpan.FromMilliseconds(200)), Is.True, "Subscribe 被 dispatch 长时间阻塞");
+
+ release.Set();
+
+ Assert.That(dispatchTask.Wait(TimeSpan.FromSeconds(2)), Is.True, "dispatch 未在释放 middleware 后完成");
+ Assert.That(store.State.Count, Is.EqualTo(1));
+ }
+
///
/// 创建一个带有基础 reducer 的测试 Store。
///
@@ -455,6 +487,25 @@ public class StoreTests
}
}
+ ///
+ /// 用于验证 dispatch 管线在 middleware 执行期间不会占用状态锁的测试中间件。
+ ///
+ private sealed class BlockingMiddleware(ManualResetEventSlim entered, ManualResetEventSlim release)
+ : IStoreMiddleware
+ {
+ ///
+ /// 通知测试线程 middleware 已进入阻塞点,并等待释放信号后继续执行。
+ ///
+ /// 当前分发上下文。
+ /// 后续处理节点。
+ public void Invoke(StoreDispatchContext context, Action next)
+ {
+ entered.Set();
+ release.Wait(TimeSpan.FromSeconds(2));
+ next();
+ }
+ }
+
///
/// 在中间件阶段尝试二次分发的测试中间件,用于验证重入保护。
///
diff --git a/GFramework.Core/StateManagement/Store.cs b/GFramework.Core/StateManagement/Store.cs
index 5d30d5b..a828535 100644
--- a/GFramework.Core/StateManagement/Store.cs
+++ b/GFramework.Core/StateManagement/Store.cs
@@ -10,8 +10,15 @@ namespace GFramework.Core.StateManagement;
/// 或需要中间件/诊断能力的状态场景,而不是替代所有简单字段级响应式属性。
///
/// 状态树的根状态类型。
-public class Store : IStore, IStoreDiagnostics
+public sealed class Store : IStore, IStoreDiagnostics
{
+ ///
+ /// Dispatch 串行化门闩。
+ /// 该锁保证任意时刻只有一个 action 管线在运行,从而保持状态演进顺序确定,
+ /// 同时避免让耗时 middleware / reducer 长时间占用状态锁。
+ ///
+ private readonly object _dispatchGate = new();
+
///
/// 当前状态变化订阅者列表。
/// 使用显式订阅对象而不是委托链,便于处理原子初始化订阅、挂起补发和精确解绑。
@@ -20,18 +27,20 @@ public class Store : IStore, IStoreDiagnostics
///
/// Store 内部所有可变状态的同步锁。
- /// 该锁同时保护订阅集合、reducer 注册表和分发过程,确保状态演进是串行且可预测的。
+ /// 该锁仅保护状态快照、订阅集合、缓存选择视图和注册表本身的短临界区访问。
///
private readonly object _lock = new();
///
/// 已注册的中间件链,按添加顺序执行。
+ /// Dispatch 开始时会抓取快照,因此运行中的分发不会受到后续注册变化影响。
///
private readonly List> _middlewares = [];
///
/// 按 action 具体运行时类型组织的 reducer 注册表。
/// Store 采用精确类型匹配策略,保证 reducer 执行顺序和行为保持确定性。
+ /// Dispatch 开始时会抓取对应 action 类型的 reducer 快照。
///
private readonly Dictionary> _reducers = [];
@@ -83,6 +92,34 @@ public class Store : IStore, IStoreDiagnostics
_stateComparer = comparer ?? EqualityComparer.Default;
}
+ ///
+ /// 获取最近一次分发的 action 类型。
+ ///
+ public Type? LastActionType
+ {
+ get
+ {
+ lock (_lock)
+ {
+ return _lastActionType;
+ }
+ }
+ }
+
+ ///
+ /// 获取最近一次真正改变状态的时间戳。
+ ///
+ public DateTimeOffset? LastStateChangedAt
+ {
+ get
+ {
+ lock (_lock)
+ {
+ return _lastStateChangedAt;
+ }
+ }
+ }
+
///
/// 获取当前状态快照。
///
@@ -97,6 +134,112 @@ public class Store : IStore, IStoreDiagnostics
}
}
+ ///
+ /// 分发一个 action 并按顺序执行匹配的 reducer。
+ ///
+ /// action 的具体类型。
+ /// 要分发的 action。
+ /// 当 为 时抛出。
+ /// 当同一 Store 发生重入分发时抛出。
+ public void Dispatch(TAction action)
+ {
+ ArgumentNullException.ThrowIfNull(action);
+
+ Action[] listenersSnapshot = Array.Empty>();
+ IStoreMiddleware[] middlewaresSnapshot = Array.Empty>();
+ IStoreReducerAdapter[] reducersSnapshot = Array.Empty();
+ IEqualityComparer stateComparerSnapshot = _stateComparer;
+ StoreDispatchContext? context = null;
+ var enteredDispatchScope = false;
+
+ lock (_dispatchGate)
+ {
+ try
+ {
+ lock (_lock)
+ {
+ EnsureNotDispatching();
+ _isDispatching = true;
+ enteredDispatchScope = true;
+ context = new StoreDispatchContext(action!, _state);
+ stateComparerSnapshot = _stateComparer;
+ middlewaresSnapshot = _middlewares.Count > 0
+ ? _middlewares.ToArray()
+ : Array.Empty>();
+ reducersSnapshot = CreateReducerSnapshot(context.ActionType);
+ }
+
+ // middleware 和 reducer 可能包含较重的同步逻辑,因此仅持有 dispatch 串行门,
+ // 不占用状态锁,让读取、订阅和注册操作只在需要访问共享状态时短暂阻塞。
+ ExecuteDispatchPipeline(context, middlewaresSnapshot, reducersSnapshot, stateComparerSnapshot);
+
+ lock (_lock)
+ {
+ _lastActionType = context.ActionType;
+ _lastDispatchRecord = new StoreDispatchRecord(
+ context.Action,
+ context.PreviousState,
+ context.NextState,
+ context.HasStateChanged,
+ context.DispatchedAt);
+
+ if (!context.HasStateChanged)
+ {
+ return;
+ }
+
+ _state = context.NextState;
+ _lastStateChangedAt = context.DispatchedAt;
+ listenersSnapshot = SnapshotListenersForNotification(context.NextState);
+ }
+ }
+ finally
+ {
+ if (enteredDispatchScope)
+ {
+ lock (_lock)
+ {
+ _isDispatching = false;
+ }
+ }
+ }
+ }
+
+ // 始终在锁外通知订阅者,避免监听器内部读取 Store 或执行额外逻辑时产生死锁。
+ foreach (var listener in listenersSnapshot)
+ {
+ listener(context!.NextState);
+ }
+ }
+
+ ///
+ /// 获取当前订阅者数量。
+ ///
+ public int SubscriberCount
+ {
+ get
+ {
+ lock (_lock)
+ {
+ return _listeners.Count;
+ }
+ }
+ }
+
+ ///
+ /// 获取最近一次分发记录。
+ ///
+ public StoreDispatchRecord? LastDispatchRecord
+ {
+ get
+ {
+ lock (_lock)
+ {
+ return _lastDispatchRecord;
+ }
+ }
+ }
+
///
/// 订阅状态变化通知。
///
@@ -198,119 +341,6 @@ public class Store : IStore, IStoreDiagnostics
}
}
- ///
- /// 分发一个 action 并按顺序执行匹配的 reducer。
- ///
- /// action 的具体类型。
- /// 要分发的 action。
- /// 当 为 时抛出。
- /// 当同一 Store 发生重入分发时抛出。
- public void Dispatch(TAction action)
- {
- ArgumentNullException.ThrowIfNull(action);
-
- Action[] listenersSnapshot = Array.Empty>();
- StoreDispatchContext? context = null;
-
- lock (_lock)
- {
- EnsureNotDispatching();
- _isDispatching = true;
-
- try
- {
- context = new StoreDispatchContext(action!, _state);
-
- // 在锁内串行执行完整分发流程,确保 reducer 与中间件看到的是一致的状态序列,
- // 并且不会因为并发写入导致 reducer 顺序失效。
- ExecuteDispatchPipeline(context);
-
- _lastActionType = context.ActionType;
- _lastDispatchRecord = new StoreDispatchRecord(
- context.Action,
- context.PreviousState,
- context.NextState,
- context.HasStateChanged,
- context.DispatchedAt);
-
- if (!context.HasStateChanged)
- {
- return;
- }
-
- _state = context.NextState;
- _lastStateChangedAt = context.DispatchedAt;
- listenersSnapshot = SnapshotListenersForNotification(context.NextState);
- }
- finally
- {
- _isDispatching = false;
- }
- }
-
- // 始终在锁外通知订阅者,避免监听器内部读取 Store 或执行额外逻辑时产生死锁。
- foreach (var listener in listenersSnapshot)
- {
- listener(context!.NextState);
- }
- }
-
- ///
- /// 获取当前订阅者数量。
- ///
- public int SubscriberCount
- {
- get
- {
- lock (_lock)
- {
- return _listeners.Count;
- }
- }
- }
-
- ///
- /// 获取最近一次分发的 action 类型。
- ///
- public Type? LastActionType
- {
- get
- {
- lock (_lock)
- {
- return _lastActionType;
- }
- }
- }
-
- ///
- /// 获取最近一次真正改变状态的时间戳。
- ///
- public DateTimeOffset? LastStateChangedAt
- {
- get
- {
- lock (_lock)
- {
- return _lastStateChangedAt;
- }
- }
- }
-
- ///
- /// 获取最近一次分发记录。
- ///
- public StoreDispatchRecord? LastDispatchRecord
- {
- get
- {
- lock (_lock)
- {
- return _lastDispatchRecord;
- }
- }
- }
-
///
/// 创建一个用于当前状态类型的 Store 构建器。
///
@@ -435,13 +465,20 @@ public class Store : IStore, IStoreDiagnostics
/// 执行一次完整分发管线。
///
/// 当前分发上下文。
- private void ExecuteDispatchPipeline(StoreDispatchContext context)
+ /// 本次分发使用的中间件快照。
+ /// 本次分发使用的 reducer 快照。
+ /// 本次分发使用的状态比较器快照。
+ private static void ExecuteDispatchPipeline(
+ StoreDispatchContext context,
+ IReadOnlyList> middlewares,
+ IReadOnlyList reducers,
+ IEqualityComparer stateComparer)
{
- Action pipeline = () => ApplyReducers(context);
+ Action pipeline = () => ApplyReducers(context, reducers, stateComparer);
- for (var i = _middlewares.Count - 1; i >= 0; i--)
+ for (var i = middlewares.Count - 1; i >= 0; i--)
{
- var middleware = _middlewares[i];
+ var middleware = middlewares[i];
var next = pipeline;
pipeline = () => middleware.Invoke(context, next);
}
@@ -454,9 +491,14 @@ public class Store : IStore, IStoreDiagnostics
/// reducer 使用 action 的精确运行时类型进行查找,以保证匹配结果和执行顺序稳定。
///
/// 当前分发上下文。
- private void ApplyReducers(StoreDispatchContext context)
+ /// 本次分发使用的 reducer 快照。
+ /// 本次分发使用的状态比较器快照。
+ private static void ApplyReducers(
+ StoreDispatchContext context,
+ IReadOnlyList reducers,
+ IEqualityComparer stateComparer)
{
- if (!_reducers.TryGetValue(context.ActionType, out var reducers) || reducers.Count == 0)
+ if (reducers.Count == 0)
{
context.NextState = context.PreviousState;
context.HasStateChanged = false;
@@ -473,7 +515,7 @@ public class Store : IStore, IStoreDiagnostics
}
context.NextState = nextState;
- context.HasStateChanged = !_stateComparer.Equals(context.PreviousState, nextState);
+ context.HasStateChanged = !stateComparer.Equals(context.PreviousState, nextState);
}
///
@@ -521,6 +563,22 @@ public class Store : IStore, IStoreDiagnostics
return activeListeners.Count > 0 ? activeListeners.ToArray() : Array.Empty>();
}
+ ///
+ /// 为当前 action 类型创建 reducer 快照。
+ /// Dispatch 在离开状态锁前复制列表,以便后续在锁外执行稳定、不可变的 reducer 序列。
+ ///
+ /// 当前分发的 action 类型。
+ /// 对应 action 类型的 reducer 快照;若未注册则返回空数组。
+ private IStoreReducerAdapter[] CreateReducerSnapshot(Type actionType)
+ {
+ if (!_reducers.TryGetValue(actionType, out var reducers) || reducers.Count == 0)
+ {
+ return Array.Empty();
+ }
+
+ return reducers.ToArray();
+ }
+
///
/// 解绑一个精确的订阅对象。
///
diff --git a/GFramework.Core/StateManagement/StoreBuilder.cs b/GFramework.Core/StateManagement/StoreBuilder.cs
index 3d75196..97d788e 100644
--- a/GFramework.Core/StateManagement/StoreBuilder.cs
+++ b/GFramework.Core/StateManagement/StoreBuilder.cs
@@ -7,7 +7,7 @@ namespace GFramework.Core.StateManagement;
/// 该类型用于在 Store 创建之前集中配置比较器、reducer 和中间件,适合模块安装和测试工厂场景。
///
/// 状态树的根状态类型。
-public class StoreBuilder : IStoreBuilder
+public sealed class StoreBuilder : IStoreBuilder
{
///
/// 延迟应用到 Store 的配置操作列表。
@@ -20,43 +20,6 @@ public class StoreBuilder : IStoreBuilder
///
private IEqualityComparer? _comparer;
- ///
- /// 配置状态比较器。
- ///
- /// 状态比较器。
- /// 当前构建器实例。
- public IStoreBuilder WithComparer(IEqualityComparer comparer)
- {
- _comparer = comparer ?? throw new ArgumentNullException(nameof(comparer));
- return this;
- }
-
- ///
- /// 添加一个强类型 reducer。
- ///
- /// 当前 reducer 处理的 action 类型。
- /// 要添加的 reducer。
- /// 当前构建器实例。
- public IStoreBuilder AddReducer(IReducer reducer)
- {
- ArgumentNullException.ThrowIfNull(reducer);
- _configurators.Add(store => store.RegisterReducer(reducer));
- return this;
- }
-
- ///
- /// 使用委托快速添加一个 reducer。
- ///
- /// 当前 reducer 处理的 action 类型。
- /// 执行归约的委托。
- /// 当前构建器实例。
- public IStoreBuilder AddReducer(Func reducer)
- {
- ArgumentNullException.ThrowIfNull(reducer);
- _configurators.Add(store => store.RegisterReducer(reducer));
- return this;
- }
-
///
/// 添加一个 Store 中间件。
///
@@ -84,4 +47,42 @@ public class StoreBuilder : IStoreBuilder
return store;
}
+
+
+ ///
+ /// 添加一个强类型 reducer。
+ ///
+ /// 当前 reducer 处理的 action 类型。
+ /// 要添加的 reducer。
+ /// 当前构建器实例。
+ public IStoreBuilder AddReducer(IReducer reducer)
+ {
+ ArgumentNullException.ThrowIfNull(reducer);
+ _configurators.Add(store => store.RegisterReducer(reducer));
+ return this;
+ }
+
+ ///
+ /// 配置状态比较器。
+ ///
+ /// 状态比较器。
+ /// 当前构建器实例。
+ public IStoreBuilder WithComparer(IEqualityComparer comparer)
+ {
+ _comparer = comparer ?? throw new ArgumentNullException(nameof(comparer));
+ return this;
+ }
+
+ ///
+ /// 使用委托快速添加一个 reducer。
+ ///
+ /// 当前 reducer 处理的 action 类型。
+ /// 执行归约的委托。
+ /// 当前构建器实例。
+ public IStoreBuilder AddReducer(Func reducer)
+ {
+ ArgumentNullException.ThrowIfNull(reducer);
+ _configurators.Add(store => store.RegisterReducer(reducer));
+ return this;
+ }
}
\ No newline at end of file
diff --git a/GFramework.Core/StateManagement/StoreSelection.cs b/GFramework.Core/StateManagement/StoreSelection.cs
index 35224a5..b912da4 100644
--- a/GFramework.Core/StateManagement/StoreSelection.cs
+++ b/GFramework.Core/StateManagement/StoreSelection.cs
@@ -12,7 +12,7 @@ namespace GFramework.Core.StateManagement;
///
/// 源状态类型。
/// 投影后的局部状态类型。
-public class StoreSelection : IReadonlyBindableProperty
+public sealed class StoreSelection : IReadonlyBindableProperty
{
///
/// 用于判断选择结果是否真正变化的比较器。