diff --git a/GFramework.Cqrs.Benchmarks/Messaging/BenchmarkHostFactory.cs b/GFramework.Cqrs.Benchmarks/Messaging/BenchmarkHostFactory.cs
index 61f3a13d..34397dc1 100644
--- a/GFramework.Cqrs.Benchmarks/Messaging/BenchmarkHostFactory.cs
+++ b/GFramework.Cqrs.Benchmarks/Messaging/BenchmarkHostFactory.cs
@@ -165,33 +165,30 @@ internal static class BenchmarkHostFactory
/// 在真实的 request 级作用域内执行一次 GFramework.CQRS request 分发。
///
/// 请求响应类型。
- /// 冻结后的 benchmark 根容器,用于创建 request 作用域并提供注册元数据。
- /// 当前 request 级 runtime 复用的日志器。
+ /// 复用的 scoped benchmark runtime。
+ /// 负责为每次 request 激活独立作用域的只读容器适配层。
/// 当前 CQRS 分发上下文。
/// 要发送的 request。
/// 取消令牌。
/// 当前 request 的响应结果。
///
- /// 该入口只服务 request lifetime benchmark:每次调用都会显式创建并释放一个新的 DI 作用域,
- /// 让 `Scoped` handler 在真实 request 边界内解析,而不是退化为根容器解析。
+ /// 该入口只服务 request lifetime benchmark:它会复用同一个 dispatcher/runtime 实例,
+ /// 但在每次调用前后显式创建并释放新的 DI 作用域,
+ /// 让 `Scoped` handler 在真实 request 边界内解析,而不是退化为根容器解析或额外计入 runtime 构造成本。
///
internal static async ValueTask SendScopedGFrameworkRequestAsync(
- MicrosoftDiContainer rootContainer,
- ILogger runtimeLogger,
+ ICqrsRuntime runtime,
+ ScopedBenchmarkContainer scopedContainer,
ICqrsContext context,
GFramework.Cqrs.Abstractions.Cqrs.IRequest request,
CancellationToken cancellationToken = default)
{
- ArgumentNullException.ThrowIfNull(rootContainer);
- ArgumentNullException.ThrowIfNull(runtimeLogger);
+ ArgumentNullException.ThrowIfNull(runtime);
+ ArgumentNullException.ThrowIfNull(scopedContainer);
ArgumentNullException.ThrowIfNull(context);
ArgumentNullException.ThrowIfNull(request);
- using var scope = rootContainer.CreateScope();
- var scopedContainer = new ScopedBenchmarkContainer(rootContainer, scope);
- var runtime = GFramework.Cqrs.CqrsRuntimeFactory.CreateRuntime(
- scopedContainer,
- runtimeLogger);
+ using var scopeLease = scopedContainer.EnterScope();
return await runtime.SendAsync(context, request, cancellationToken).ConfigureAwait(false);
}
@@ -223,8 +220,8 @@ internal static class BenchmarkHostFactory
/// 在真实的 request 级作用域内创建一次 GFramework.CQRS stream,并让该作用域覆盖整个异步枚举周期。
///
/// stream 响应元素类型。
- /// 冻结后的 benchmark 根容器,用于创建 request 作用域并提供注册元数据。
- /// 当前 request 级 runtime 复用的日志器。
+ /// 复用的 scoped benchmark runtime。
+ /// 负责为每次 stream 激活独立作用域的只读容器适配层。
/// 当前 CQRS 分发上下文。
/// 要创建 stream 的 request。
/// 取消令牌。
@@ -235,18 +232,18 @@ internal static class BenchmarkHostFactory
/// 避免 `Scoped` handler 退化成“建流后立刻释放 scope,再在根容器语义下继续枚举”的错误模型。
///
internal static IAsyncEnumerable CreateScopedGFrameworkStream(
- MicrosoftDiContainer rootContainer,
- ILogger runtimeLogger,
+ ICqrsRuntime runtime,
+ ScopedBenchmarkContainer scopedContainer,
ICqrsContext context,
GFramework.Cqrs.Abstractions.Cqrs.IStreamRequest request,
CancellationToken cancellationToken = default)
{
- ArgumentNullException.ThrowIfNull(rootContainer);
- ArgumentNullException.ThrowIfNull(runtimeLogger);
+ ArgumentNullException.ThrowIfNull(runtime);
+ ArgumentNullException.ThrowIfNull(scopedContainer);
ArgumentNullException.ThrowIfNull(context);
ArgumentNullException.ThrowIfNull(request);
- return EnumerateScopedGFrameworkStreamAsync(rootContainer, runtimeLogger, context, request, cancellationToken);
+ return EnumerateScopedGFrameworkStreamAsync(runtime, scopedContainer, context, request, cancellationToken);
}
///
@@ -279,7 +276,7 @@ internal static class BenchmarkHostFactory
/// 可直接解析 generated `Mediator.Mediator` 的 DI 宿主。
///
/// 当前 benchmark 只把 `Mediator` 作为单例 steady-state 对照组接入,
- /// 因为它的 lifetime 由 source generator 在编译期塑形;若后续需要 `Transient` / `Scoped` 矩阵,
+ /// 因为它的 lifetime 由 source generator 在编译期塑形;若后续需要 `Transient` / `Scoped` 矩阵,
/// 应按 `Mediator` 官方 benchmark 的做法拆成独立 build config,而不是在同一编译产物里混用多个 lifetime。
///
internal static ServiceProvider CreateMediatorServiceProvider(Action? configure)
@@ -310,17 +307,13 @@ internal static class BenchmarkHostFactory
/// 在单个显式作用域内创建并枚举 GFramework.CQRS stream。
///
private static async IAsyncEnumerable EnumerateScopedGFrameworkStreamAsync(
- MicrosoftDiContainer rootContainer,
- ILogger runtimeLogger,
+ ICqrsRuntime runtime,
+ ScopedBenchmarkContainer scopedContainer,
ICqrsContext context,
GFramework.Cqrs.Abstractions.Cqrs.IStreamRequest request,
[System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken)
{
- using var scope = rootContainer.CreateScope();
- var scopedContainer = new ScopedBenchmarkContainer(rootContainer, scope);
- var runtime = GFramework.Cqrs.CqrsRuntimeFactory.CreateRuntime(
- scopedContainer,
- runtimeLogger);
+ using var scopeLease = scopedContainer.EnterScope();
var stream = runtime.CreateStream(context, request, cancellationToken);
await foreach (var response in stream.ConfigureAwait(false))
diff --git a/GFramework.Cqrs.Benchmarks/Messaging/RequestLifetimeBenchmarks.cs b/GFramework.Cqrs.Benchmarks/Messaging/RequestLifetimeBenchmarks.cs
index 8a9458b0..4c0e5306 100644
--- a/GFramework.Cqrs.Benchmarks/Messaging/RequestLifetimeBenchmarks.cs
+++ b/GFramework.Cqrs.Benchmarks/Messaging/RequestLifetimeBenchmarks.cs
@@ -32,6 +32,8 @@ public class RequestLifetimeBenchmarks
{
private MicrosoftDiContainer _container = null!;
private ICqrsRuntime? _runtime;
+ private ScopedBenchmarkContainer? _scopedContainer;
+ private ICqrsRuntime? _scopedRuntime;
private ServiceProvider _serviceProvider = null!;
private IMediator? _mediatr;
private BenchmarkRequestHandler _baselineHandler = null!;
@@ -111,6 +113,13 @@ public class RequestLifetimeBenchmarks
_container,
_runtimeLogger);
}
+ else
+ {
+ _scopedContainer = new ScopedBenchmarkContainer(_container);
+ _scopedRuntime = GFramework.Cqrs.CqrsRuntimeFactory.CreateRuntime(
+ _scopedContainer,
+ _runtimeLogger);
+ }
_serviceProvider = BenchmarkHostFactory.CreateMediatRServiceProvider(
configure: null,
@@ -157,8 +166,8 @@ public class RequestLifetimeBenchmarks
if (Lifetime == HandlerLifetime.Scoped)
{
return BenchmarkHostFactory.SendScopedGFrameworkRequestAsync(
- _container,
- _runtimeLogger,
+ _scopedRuntime!,
+ _scopedContainer!,
BenchmarkContext.Instance,
_request,
CancellationToken.None);
diff --git a/GFramework.Cqrs.Benchmarks/Messaging/ScopedBenchmarkContainer.cs b/GFramework.Cqrs.Benchmarks/Messaging/ScopedBenchmarkContainer.cs
index 29c51d71..536c7009 100644
--- a/GFramework.Cqrs.Benchmarks/Messaging/ScopedBenchmarkContainer.cs
+++ b/GFramework.Cqrs.Benchmarks/Messaging/ScopedBenchmarkContainer.cs
@@ -14,34 +14,53 @@ using Microsoft.Extensions.DependencyInjection;
namespace GFramework.Cqrs.Benchmarks.Messaging;
///
-/// 把冻结后的 benchmark 根容器与单个 组合成 request 级解析视图。
+/// 把冻结后的 benchmark 根容器适配成可重复进入的 request 级解析视图。
///
///
/// `CqrsDispatcher` 会直接依赖 做 handler / pipeline 解析,
-/// 因此 request lifetime benchmark 需要一个既保留根容器注册元数据,又把实例解析切换到显式作用域 provider
-/// 的最小适配层。该类型只覆盖 benchmark 当前 request 路径会使用到的解析相关入口;
+/// 因此 request lifetime benchmark 需要一个既保留根容器注册元数据,又能在每次 benchmark 调用时把实例解析切换到
+/// 显式作用域 provider 的最小适配层。该类型只覆盖 benchmark 当前 request 路径会使用到的解析相关入口;
/// 任何注册、清空或冻结修改操作都应继续发生在根容器构建阶段,因此这里统一拒绝可变更 API。
///
internal sealed class ScopedBenchmarkContainer : IIocContainer
{
private readonly MicrosoftDiContainer _rootContainer;
- private readonly IServiceProvider _scopedProvider;
+ private IServiceScope? _activeScope;
+ private IServiceProvider? _scopedProvider;
///
/// 初始化一个绑定到单个 request 作用域的 benchmark 容器适配器。
///
/// 已冻结的 benchmark 根容器。
- /// 当前 request 独占的作用域实例。
- internal ScopedBenchmarkContainer(MicrosoftDiContainer rootContainer, IServiceScope scope)
+ internal ScopedBenchmarkContainer(MicrosoftDiContainer rootContainer)
{
_rootContainer = rootContainer ?? throw new ArgumentNullException(nameof(rootContainer));
- ArgumentNullException.ThrowIfNull(scope);
- _scopedProvider = scope.ServiceProvider;
+ }
+
+ ///
+ /// 为当前 benchmark 调用创建并持有一个新的 request 级作用域。
+ ///
+ /// 离开作用域时负责释放本次 request 级作用域的租约。
+ /// 当前适配器仍持有上一次尚未释放的作用域。
+ internal ScopeLease EnterScope()
+ {
+ if (_activeScope is not null)
+ {
+ throw new InvalidOperationException(
+ "Scoped benchmark containers do not support overlapping active scopes.");
+ }
+
+ _activeScope = _rootContainer.CreateScope();
+ _scopedProvider = _activeScope.ServiceProvider;
+ return new ScopeLease(this);
}
///
/// 当前适配器不支持在 request 作用域内追加注册。
///
+ /// 实例类型。
+ /// 原本要注册到根容器中的单例实例。
+ /// 当前适配器始终为只读视图。
public void RegisterSingleton(T instance)
{
throw CreateMutationNotSupportedException();
@@ -50,6 +69,9 @@ internal sealed class ScopedBenchmarkContainer : IIocContainer
///
/// 当前适配器不支持在 request 作用域内追加注册。
///
+ /// 服务契约类型。
+ /// 服务实现类型。
+ /// 当前适配器始终为只读视图。
public void RegisterSingleton()
where TImpl : class, TService
where TService : class
@@ -60,6 +82,9 @@ internal sealed class ScopedBenchmarkContainer : IIocContainer
///
/// 当前适配器不支持在 request 作用域内追加注册。
///
+ /// 服务契约类型。
+ /// 服务实现类型。
+ /// 当前适配器始终为只读视图。
public void RegisterTransient()
where TImpl : class, TService
where TService : class
@@ -70,6 +95,9 @@ internal sealed class ScopedBenchmarkContainer : IIocContainer
///
/// 当前适配器不支持在 request 作用域内追加注册。
///
+ /// 服务契约类型。
+ /// 服务实现类型。
+ /// 当前适配器始终为只读视图。
public void RegisterScoped()
where TImpl : class, TService
where TService : class
@@ -80,6 +108,8 @@ internal sealed class ScopedBenchmarkContainer : IIocContainer
///
/// 当前适配器不支持在 request 作用域内追加注册。
///
+ /// 原本要附加到复数注册集合中的实例。
+ /// 当前适配器始终为只读视图。
public void RegisterPlurality(object instance)
{
throw CreateMutationNotSupportedException();
@@ -88,6 +118,8 @@ internal sealed class ScopedBenchmarkContainer : IIocContainer
///
/// 当前适配器不支持在 request 作用域内追加注册。
///
+ /// 复数注册项类型。
+ /// 当前适配器始终为只读视图。
public void RegisterPlurality() where T : class
{
throw CreateMutationNotSupportedException();
@@ -96,6 +128,8 @@ internal sealed class ScopedBenchmarkContainer : IIocContainer
///
/// 当前适配器不支持在 request 作用域内追加注册。
///
+ /// 原本要注册到容器中的系统实例。
+ /// 当前适配器始终为只读视图。
public void RegisterSystem(ISystem system)
{
throw CreateMutationNotSupportedException();
@@ -104,6 +138,9 @@ internal sealed class ScopedBenchmarkContainer : IIocContainer
///
/// 当前适配器不支持在 request 作用域内追加注册。
///
+ /// 实例类型。
+ /// 原本要注册到容器中的实例。
+ /// 当前适配器始终为只读视图。
public void Register(T instance)
{
throw CreateMutationNotSupportedException();
@@ -112,6 +149,9 @@ internal sealed class ScopedBenchmarkContainer : IIocContainer
///
/// 当前适配器不支持在 request 作用域内追加注册。
///
+ /// 原本要绑定的服务类型。
+ /// 原本要绑定到该类型的实例。
+ /// 当前适配器始终为只读视图。
public void Register(Type type, object instance)
{
throw CreateMutationNotSupportedException();
@@ -120,6 +160,9 @@ internal sealed class ScopedBenchmarkContainer : IIocContainer
///
/// 当前适配器不支持在 request 作用域内追加注册。
///
+ /// 工厂要创建的服务类型。
+ /// 原本要注册的工厂委托。
+ /// 当前适配器始终为只读视图。
public void RegisterFactory(Func factory) where TService : class
{
throw CreateMutationNotSupportedException();
@@ -128,6 +171,8 @@ internal sealed class ScopedBenchmarkContainer : IIocContainer
///
/// 当前适配器不支持在 request 作用域内追加注册。
///
+ /// 原本要注册的 request pipeline 行为类型。
+ /// 当前适配器始终为只读视图。
public void RegisterCqrsPipelineBehavior() where TBehavior : class
{
throw CreateMutationNotSupportedException();
@@ -136,6 +181,8 @@ internal sealed class ScopedBenchmarkContainer : IIocContainer
///
/// 当前适配器不支持在 request 作用域内追加注册。
///
+ /// 原本要注册的 stream pipeline 行为类型。
+ /// 当前适配器始终为只读视图。
public void RegisterCqrsStreamPipelineBehavior() where TBehavior : class
{
throw CreateMutationNotSupportedException();
@@ -144,6 +191,8 @@ internal sealed class ScopedBenchmarkContainer : IIocContainer
///
/// 当前适配器不支持在 request 作用域内追加注册。
///
+ /// 原本要扫描 CQRS handler 的程序集。
+ /// 当前适配器始终为只读视图。
public void RegisterCqrsHandlersFromAssembly(System.Reflection.Assembly assembly)
{
throw CreateMutationNotSupportedException();
@@ -152,6 +201,8 @@ internal sealed class ScopedBenchmarkContainer : IIocContainer
///
/// 当前适配器不支持在 request 作用域内追加注册。
///
+ /// 原本要扫描 CQRS handler 的程序集集合。
+ /// 当前适配器始终为只读视图。
public void RegisterCqrsHandlersFromAssemblies(IEnumerable assemblies)
{
throw CreateMutationNotSupportedException();
@@ -160,6 +211,8 @@ internal sealed class ScopedBenchmarkContainer : IIocContainer
///
/// 当前适配器不支持执行额外的服务配置钩子。
///
+ /// 原本要执行的服务配置委托。
+ /// 当前适配器始终为只读视图。
public void ExecuteServicesHook(Action? configurator = null)
{
throw CreateMutationNotSupportedException();
@@ -168,57 +221,87 @@ internal sealed class ScopedBenchmarkContainer : IIocContainer
///
/// 从当前 request 作用域解析单个服务实例。
///
+ /// 目标服务类型。
+ /// 解析到的服务实例;若当前作用域未注册则返回 。
+ /// 调用方尚未通过 激活作用域。
public T? Get() where T : class
{
- return _scopedProvider.GetService();
+ return GetScopedProvider().GetService();
}
///
/// 从当前 request 作用域解析单个服务实例。
///
+ /// 目标服务类型。
+ /// 解析到的服务实例;若当前作用域未注册则返回 。
+ /// 为 。
+ /// 调用方尚未通过 激活作用域。
public object? Get(Type type)
{
ArgumentNullException.ThrowIfNull(type);
- return _scopedProvider.GetService(type);
+ return GetScopedProvider().GetService(type);
}
///
/// 从当前 request 作用域解析必需的单个服务实例。
///
+ /// 目标服务类型。
+ /// 解析到的服务实例。
+ ///
+ /// 调用方尚未通过 激活作用域,或当前作用域缺少必需服务。
+ ///
public T GetRequired() where T : class
{
- return _scopedProvider.GetRequiredService();
+ return GetScopedProvider().GetRequiredService();
}
///
/// 从当前 request 作用域解析必需的单个服务实例。
///
+ /// 目标服务类型。
+ /// 解析到的服务实例。
+ /// 为 。
+ ///
+ /// 调用方尚未通过 激活作用域,或当前作用域缺少必需服务。
+ ///
public object GetRequired(Type type)
{
ArgumentNullException.ThrowIfNull(type);
- return _scopedProvider.GetRequiredService(type);
+ return GetScopedProvider().GetRequiredService(type);
}
///
/// 从当前 request 作用域解析全部服务实例。
///
+ /// 目标服务类型。
+ /// 当前作用域中该服务类型的全部实例。
+ /// 调用方尚未通过 激活作用域。
public IReadOnlyList GetAll() where T : class
{
- return _scopedProvider.GetServices().ToList();
+ return GetScopedProvider().GetServices().ToList();
}
///
/// 从当前 request 作用域解析全部服务实例。
///
+ /// 目标服务类型。
+ /// 当前作用域中该服务类型的全部实例。
+ /// 为 。
+ /// 调用方尚未通过 激活作用域。
public IReadOnlyList