diff --git a/GFramework.Cqrs.Benchmarks/Messaging/BenchmarkHostFactory.cs b/GFramework.Cqrs.Benchmarks/Messaging/BenchmarkHostFactory.cs
index fb93cc32..57d46d23 100644
--- a/GFramework.Cqrs.Benchmarks/Messaging/BenchmarkHostFactory.cs
+++ b/GFramework.Cqrs.Benchmarks/Messaging/BenchmarkHostFactory.cs
@@ -3,6 +3,8 @@
using System;
using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
using GFramework.Core.Abstractions.Logging;
using GFramework.Core.Ioc;
using GFramework.Cqrs.Abstractions.Cqrs;
@@ -158,6 +160,64 @@ internal static class BenchmarkHostFactory
return services.BuildServiceProvider();
}
+ ///
+ /// 在真实的 request 级作用域内执行一次 GFramework.CQRS request 分发。
+ ///
+ /// 请求响应类型。
+ /// 冻结后的 benchmark 根容器,用于创建 request 作用域并提供注册元数据。
+ /// 当前 request 级 runtime 复用的日志器。
+ /// 当前 CQRS 分发上下文。
+ /// 要发送的 request。
+ /// 取消令牌。
+ /// 当前 request 的响应结果。
+ ///
+ /// 该入口只服务 request lifetime benchmark:每次调用都会显式创建并释放一个新的 DI 作用域,
+ /// 让 `Scoped` handler 在真实 request 边界内解析,而不是退化为根容器解析。
+ ///
+ internal static async ValueTask SendScopedGFrameworkRequestAsync(
+ MicrosoftDiContainer rootContainer,
+ ILogger runtimeLogger,
+ ICqrsContext context,
+ GFramework.Cqrs.Abstractions.Cqrs.IRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ ArgumentNullException.ThrowIfNull(rootContainer);
+ ArgumentNullException.ThrowIfNull(runtimeLogger);
+ 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);
+ return await runtime.SendAsync(context, request, cancellationToken).ConfigureAwait(false);
+ }
+
+ ///
+ /// 在真实的 request 级作用域内执行一次 MediatR request 分发。
+ ///
+ /// 请求响应类型。
+ /// 当前 benchmark 的根 。
+ /// 要发送的 request。
+ /// 取消令牌。
+ /// 当前 request 的响应结果。
+ ///
+ /// 这里显式从新的 scope 解析 ,确保 `Scoped` handler 与其依赖绑定到 request 边界。
+ ///
+ internal static async Task SendScopedMediatRRequestAsync(
+ ServiceProvider rootServiceProvider,
+ MediatR.IRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ ArgumentNullException.ThrowIfNull(rootServiceProvider);
+ ArgumentNullException.ThrowIfNull(request);
+
+ using var scope = rootServiceProvider.CreateScope();
+ var mediator = scope.ServiceProvider.GetRequiredService();
+ return await mediator.Send(request, cancellationToken).ConfigureAwait(false);
+ }
+
///
/// 创建承载 NuGet `Mediator` source-generated concrete mediator 的最小对照宿主。
///
diff --git a/GFramework.Cqrs.Benchmarks/Messaging/RequestLifetimeBenchmarks.cs b/GFramework.Cqrs.Benchmarks/Messaging/RequestLifetimeBenchmarks.cs
index 2e342dd6..8a9458b0 100644
--- a/GFramework.Cqrs.Benchmarks/Messaging/RequestLifetimeBenchmarks.cs
+++ b/GFramework.Cqrs.Benchmarks/Messaging/RequestLifetimeBenchmarks.cs
@@ -23,24 +23,25 @@ namespace GFramework.Cqrs.Benchmarks.Messaging;
/// 对比 request steady-state dispatch 在不同 handler 生命周期下的额外开销。
///
///
-/// 当前矩阵只覆盖 `Singleton` 与 `Transient`。
-/// `Scoped` 在两个 runtime 中都依赖显式作用域边界,而当前 benchmark 宿主故意保持“单根容器最小宿主”模型,
-/// 直接把 scoped 解析压到根作用域会让对照语义失真,因此留到未来有真实 scoped host 基线时再扩展。
+/// 当前矩阵覆盖 `Singleton`、`Scoped` 与 `Transient`。
+/// 其中 `Scoped` 会在每次 request 分发时显式创建并释放真实的 DI 作用域,
+/// 避免把 scoped handler 错误地压到根容器解析而扭曲生命周期对照。
///
[Config(typeof(Config))]
public class RequestLifetimeBenchmarks
{
private MicrosoftDiContainer _container = null!;
- private ICqrsRuntime _runtime = null!;
+ private ICqrsRuntime? _runtime;
private ServiceProvider _serviceProvider = null!;
- private IMediator _mediatr = null!;
+ private IMediator? _mediatr;
private BenchmarkRequestHandler _baselineHandler = null!;
private BenchmarkRequest _request = null!;
+ private ILogger _runtimeLogger = null!;
///
/// 控制当前 benchmark 使用的 handler 生命周期。
///
- [Params(HandlerLifetime.Singleton, HandlerLifetime.Transient)]
+ [Params(HandlerLifetime.Singleton, HandlerLifetime.Scoped, HandlerLifetime.Transient)]
public HandlerLifetime Lifetime { get; set; }
///
@@ -53,6 +54,11 @@ public class RequestLifetimeBenchmarks
///
Singleton,
+ ///
+ /// 每次 request 在显式作用域内解析并复用 handler 实例。
+ ///
+ Scoped,
+
///
/// 每次分发都重新解析新的 handler 实例。
///
@@ -90,6 +96,8 @@ public class RequestLifetimeBenchmarks
_baselineHandler = new BenchmarkRequestHandler();
_request = new BenchmarkRequest(Guid.NewGuid());
+ _runtimeLogger = LoggerFactoryResolver.Provider.CreateLogger(nameof(RequestLifetimeBenchmarks) + "." + Lifetime);
+
_container = BenchmarkHostFactory.CreateFrozenGFrameworkContainer(container =>
{
BenchmarkHostFactory.RegisterGeneratedBenchmarkRegistry(container);
@@ -97,16 +105,22 @@ public class RequestLifetimeBenchmarks
});
// 容器内已提前保留默认 runtime 以支撑 generated registry 接线;
// 这里额外创建带生命周期后缀的 runtime,只是为了区分不同 benchmark 矩阵的 dispatcher 日志。
- _runtime = GFramework.Cqrs.CqrsRuntimeFactory.CreateRuntime(
- _container,
- LoggerFactoryResolver.Provider.CreateLogger(nameof(RequestLifetimeBenchmarks) + "." + Lifetime));
+ if (Lifetime != HandlerLifetime.Scoped)
+ {
+ _runtime = GFramework.Cqrs.CqrsRuntimeFactory.CreateRuntime(
+ _container,
+ _runtimeLogger);
+ }
_serviceProvider = BenchmarkHostFactory.CreateMediatRServiceProvider(
configure: null,
typeof(RequestLifetimeBenchmarks),
static candidateType => candidateType == typeof(BenchmarkRequestHandler),
ResolveMediatRLifetime(Lifetime));
- _mediatr = _serviceProvider.GetRequiredService();
+ if (Lifetime != HandlerLifetime.Scoped)
+ {
+ _mediatr = _serviceProvider.GetRequiredService();
+ }
}
///
@@ -140,7 +154,17 @@ public class RequestLifetimeBenchmarks
[Benchmark]
public ValueTask SendRequest_GFrameworkCqrs()
{
- return _runtime.SendAsync(BenchmarkContext.Instance, _request, CancellationToken.None);
+ if (Lifetime == HandlerLifetime.Scoped)
+ {
+ return BenchmarkHostFactory.SendScopedGFrameworkRequestAsync(
+ _container,
+ _runtimeLogger,
+ BenchmarkContext.Instance,
+ _request,
+ CancellationToken.None);
+ }
+
+ return _runtime!.SendAsync(BenchmarkContext.Instance, _request, CancellationToken.None);
}
///
@@ -149,7 +173,15 @@ public class RequestLifetimeBenchmarks
[Benchmark]
public Task SendRequest_MediatR()
{
- return _mediatr.Send(_request, CancellationToken.None);
+ if (Lifetime == HandlerLifetime.Scoped)
+ {
+ return BenchmarkHostFactory.SendScopedMediatRRequestAsync(
+ _serviceProvider,
+ _request,
+ CancellationToken.None);
+ }
+
+ return _mediatr!.Send(_request, CancellationToken.None);
}
///
@@ -171,6 +203,10 @@ public class RequestLifetimeBenchmarks
container.RegisterSingleton, BenchmarkRequestHandler>();
return;
+ case HandlerLifetime.Scoped:
+ container.RegisterScoped, BenchmarkRequestHandler>();
+ return;
+
case HandlerLifetime.Transient:
container.RegisterTransient, BenchmarkRequestHandler>();
return;
@@ -189,6 +225,7 @@ public class RequestLifetimeBenchmarks
return lifetime switch
{
HandlerLifetime.Singleton => ServiceLifetime.Singleton,
+ HandlerLifetime.Scoped => ServiceLifetime.Scoped,
HandlerLifetime.Transient => ServiceLifetime.Transient,
_ => throw new ArgumentOutOfRangeException(nameof(lifetime), lifetime, "Unsupported benchmark handler lifetime.")
};
diff --git a/GFramework.Cqrs.Benchmarks/Messaging/ScopedBenchmarkContainer.cs b/GFramework.Cqrs.Benchmarks/Messaging/ScopedBenchmarkContainer.cs
new file mode 100644
index 00000000..29c51d71
--- /dev/null
+++ b/GFramework.Cqrs.Benchmarks/Messaging/ScopedBenchmarkContainer.cs
@@ -0,0 +1,361 @@
+// Copyright (c) 2025-2026 GeWuYou
+// SPDX-License-Identifier: Apache-2.0
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using GFramework.Core.Abstractions.Bases;
+using GFramework.Core.Abstractions.Ioc;
+using GFramework.Core.Abstractions.Rule;
+using GFramework.Core.Abstractions.Systems;
+using GFramework.Core.Ioc;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace GFramework.Cqrs.Benchmarks.Messaging;
+
+///
+/// 把冻结后的 benchmark 根容器与单个 组合成 request 级解析视图。
+///
+///
+/// `CqrsDispatcher` 会直接依赖 做 handler / pipeline 解析,
+/// 因此 request lifetime benchmark 需要一个既保留根容器注册元数据,又把实例解析切换到显式作用域 provider
+/// 的最小适配层。该类型只覆盖 benchmark 当前 request 路径会使用到的解析相关入口;
+/// 任何注册、清空或冻结修改操作都应继续发生在根容器构建阶段,因此这里统一拒绝可变更 API。
+///
+internal sealed class ScopedBenchmarkContainer : IIocContainer
+{
+ private readonly MicrosoftDiContainer _rootContainer;
+ private readonly IServiceProvider _scopedProvider;
+
+ ///
+ /// 初始化一个绑定到单个 request 作用域的 benchmark 容器适配器。
+ ///
+ /// 已冻结的 benchmark 根容器。
+ /// 当前 request 独占的作用域实例。
+ internal ScopedBenchmarkContainer(MicrosoftDiContainer rootContainer, IServiceScope scope)
+ {
+ _rootContainer = rootContainer ?? throw new ArgumentNullException(nameof(rootContainer));
+ ArgumentNullException.ThrowIfNull(scope);
+ _scopedProvider = scope.ServiceProvider;
+ }
+
+ ///
+ /// 当前适配器不支持在 request 作用域内追加注册。
+ ///
+ public void RegisterSingleton(T instance)
+ {
+ throw CreateMutationNotSupportedException();
+ }
+
+ ///
+ /// 当前适配器不支持在 request 作用域内追加注册。
+ ///
+ public void RegisterSingleton()
+ where TImpl : class, TService
+ where TService : class
+ {
+ throw CreateMutationNotSupportedException();
+ }
+
+ ///
+ /// 当前适配器不支持在 request 作用域内追加注册。
+ ///
+ public void RegisterTransient()
+ where TImpl : class, TService
+ where TService : class
+ {
+ throw CreateMutationNotSupportedException();
+ }
+
+ ///
+ /// 当前适配器不支持在 request 作用域内追加注册。
+ ///
+ public void RegisterScoped()
+ where TImpl : class, TService
+ where TService : class
+ {
+ throw CreateMutationNotSupportedException();
+ }
+
+ ///
+ /// 当前适配器不支持在 request 作用域内追加注册。
+ ///
+ public void RegisterPlurality(object instance)
+ {
+ throw CreateMutationNotSupportedException();
+ }
+
+ ///
+ /// 当前适配器不支持在 request 作用域内追加注册。
+ ///
+ public void RegisterPlurality() where T : class
+ {
+ throw CreateMutationNotSupportedException();
+ }
+
+ ///
+ /// 当前适配器不支持在 request 作用域内追加注册。
+ ///
+ public void RegisterSystem(ISystem system)
+ {
+ throw CreateMutationNotSupportedException();
+ }
+
+ ///
+ /// 当前适配器不支持在 request 作用域内追加注册。
+ ///
+ public void Register(T instance)
+ {
+ throw CreateMutationNotSupportedException();
+ }
+
+ ///
+ /// 当前适配器不支持在 request 作用域内追加注册。
+ ///
+ public void Register(Type type, object instance)
+ {
+ throw CreateMutationNotSupportedException();
+ }
+
+ ///
+ /// 当前适配器不支持在 request 作用域内追加注册。
+ ///
+ public void RegisterFactory(Func factory) where TService : class
+ {
+ throw CreateMutationNotSupportedException();
+ }
+
+ ///
+ /// 当前适配器不支持在 request 作用域内追加注册。
+ ///
+ public void RegisterCqrsPipelineBehavior() where TBehavior : class
+ {
+ throw CreateMutationNotSupportedException();
+ }
+
+ ///
+ /// 当前适配器不支持在 request 作用域内追加注册。
+ ///
+ public void RegisterCqrsStreamPipelineBehavior() where TBehavior : class
+ {
+ throw CreateMutationNotSupportedException();
+ }
+
+ ///
+ /// 当前适配器不支持在 request 作用域内追加注册。
+ ///
+ public void RegisterCqrsHandlersFromAssembly(System.Reflection.Assembly assembly)
+ {
+ throw CreateMutationNotSupportedException();
+ }
+
+ ///
+ /// 当前适配器不支持在 request 作用域内追加注册。
+ ///
+ public void RegisterCqrsHandlersFromAssemblies(IEnumerable assemblies)
+ {
+ throw CreateMutationNotSupportedException();
+ }
+
+ ///
+ /// 当前适配器不支持执行额外的服务配置钩子。
+ ///
+ public void ExecuteServicesHook(Action? configurator = null)
+ {
+ throw CreateMutationNotSupportedException();
+ }
+
+ ///
+ /// 从当前 request 作用域解析单个服务实例。
+ ///
+ public T? Get() where T : class
+ {
+ return _scopedProvider.GetService();
+ }
+
+ ///
+ /// 从当前 request 作用域解析单个服务实例。
+ ///
+ public object? Get(Type type)
+ {
+ ArgumentNullException.ThrowIfNull(type);
+ return _scopedProvider.GetService(type);
+ }
+
+ ///
+ /// 从当前 request 作用域解析必需的单个服务实例。
+ ///
+ public T GetRequired() where T : class
+ {
+ return _scopedProvider.GetRequiredService();
+ }
+
+ ///
+ /// 从当前 request 作用域解析必需的单个服务实例。
+ ///
+ public object GetRequired(Type type)
+ {
+ ArgumentNullException.ThrowIfNull(type);
+ return _scopedProvider.GetRequiredService(type);
+ }
+
+ ///
+ /// 从当前 request 作用域解析全部服务实例。
+ ///
+ public IReadOnlyList GetAll() where T : class
+ {
+ return _scopedProvider.GetServices().ToList();
+ }
+
+ ///
+ /// 从当前 request 作用域解析全部服务实例。
+ ///
+ public IReadOnlyList