diff --git a/.agents/skills/gframework-batch-boot/SKILL.md b/.agents/skills/gframework-batch-boot/SKILL.md
index 860ca496..e10173f7 100644
--- a/.agents/skills/gframework-batch-boot/SKILL.md
+++ b/.agents/skills/gframework-batch-boot/SKILL.md
@@ -12,6 +12,10 @@ batches until a clear stop condition is met.
Treat `AGENTS.md` as the source of truth. This skill extends `gframework-boot`; it does not replace it.
+Context budget is a first-class stop signal. Do not keep batching merely because a file-count threshold still has
+headroom if the active conversation, loaded repo artifacts, validation output, and pending recovery updates suggest the
+agent is approaching its safe working-context limit.
+
## Startup Workflow
1. Execute the normal `gframework-boot` startup sequence first:
@@ -28,6 +32,11 @@ Treat `AGENTS.md` as the source of truth. This skill extends `gframework-boot`;
- repeated test refactor pattern
- module-by-module documentation refresh
- other repetitive multi-file cleanup
+4. Before the first implementation batch, estimate whether the current task is likely to stay below roughly 80% of the
+ agent's safe working-context budget through one more full batch cycle:
+ - include already loaded `AGENTS.md`, skills, `ai-plan` files, recent command output, active diffs, and expected validation output
+ - if another batch would probably push the conversation near the limit, plan to stop after the current batch even if
+ branch-size thresholds still have room
## Baseline Selection
@@ -67,8 +76,15 @@ For shorthand numeric thresholds, use a fixed default baseline:
Choose one primary stop condition before the first batch and restate it to the user.
+When the user does not explicitly override the priority order, use:
+
+1. context-budget safety
+2. semantic batch boundary / reviewability
+3. the user-requested local metric such as files, lines, warnings, or time
+
Common stop conditions:
+- the next batch would likely push the agent above roughly 80% of its safe working-context budget
- branch diff vs baseline approaches a file-count threshold
- warnings-only build reaches a target count
- a specific hotspot list is exhausted
@@ -76,6 +92,9 @@ Common stop conditions:
If multiple stop conditions exist, rank them and treat one as primary.
+Treat file-count or line-count thresholds as coarse repository-scope signals, not as a proxy for AI context health.
+When they disagree with context-budget safety, context-budget safety wins.
+
## Shorthand Stop-Condition Syntax
`gframework-batch-boot` may be invoked with shorthand numeric thresholds when the user clearly wants a branch-size stop
@@ -108,6 +127,7 @@ When shorthand is used:
- current branch and active topic
- selected baseline
- current stop-condition metric
+ - current context-budget posture and whether one more batch is safe
- next candidate slices
2. Keep the critical path local.
3. Delegate only bounded slices with explicit ownership:
@@ -128,6 +148,7 @@ When shorthand is used:
- integrate or verify the result
- rerun the required validation
- recompute the primary stop-condition metric
+ - reassess whether one more batch would likely push the agent near or beyond roughly 80% context usage
- decide immediately whether to continue or stop
7. Do not require the user to manually trigger every round unless:
- the next slice is ambiguous
@@ -158,6 +179,7 @@ For multi-batch work, keep recovery artifacts current.
Stop the loop when any of the following becomes true:
+- the next batch would likely push the agent near or beyond roughly 80% of its safe working-context budget
- the primary stop condition has been reached or exceeded
- the remaining slices are no longer low-risk
- validation failures indicate the task is no longer repetitive
@@ -165,6 +187,7 @@ Stop the loop when any of the following becomes true:
When stopping, report:
+- whether context budget was the deciding factor
- which baseline was used
- the exact metric value at stop time
- completed batches
diff --git a/.agents/skills/gframework-boot/SKILL.md b/.agents/skills/gframework-boot/SKILL.md
index 55e9b55f..563651bd 100644
--- a/.agents/skills/gframework-boot/SKILL.md
+++ b/.agents/skills/gframework-boot/SKILL.md
@@ -36,14 +36,18 @@ Treat `AGENTS.md` as the source of truth. Use this skill to enforce a startup se
- `simple`: one concern, one file or module, no parallel discovery required
- `medium`: a small number of modules, some read-only exploration helpful, critical path still easy to keep local
- `complex`: cross-module design, migration, large refactor, or work likely to exceed one context window
-11. Apply the delegation policy from `AGENTS.md`:
+11. Estimate the current context-budget posture before substantive execution:
+ - account for loaded startup artifacts, active `ai-plan` files, visible diffs, open validation output, and likely next-step output volume
+ - if the task already appears near roughly 80% of a safe working-context budget, prefer closing the current batch,
+ refreshing recovery artifacts, and stopping at the next natural semantic boundary instead of starting a fresh broad slice
+12. Apply the delegation policy from `AGENTS.md`:
- Keep the critical path local
- Use `explorer` with `gpt-5.1-codex-mini` for narrow read-only questions, tracing, inventory, and comparisons
- Use `worker` with `gpt-5.4` only for bounded implementation tasks with explicit ownership
- Do not delegate purely for ceremony; delegate only when it materially shortens the task or controls context growth
-12. Before editing files, tell the user what you read, how you classified the task, whether subagents will be used,
+13. Before editing files, tell the user what you read, how you classified the task, whether subagents will be used,
and the first implementation step.
-13. Proceed with execution, validation, and documentation updates required by `AGENTS.md`.
+14. Proceed with execution, validation, and documentation updates required by `AGENTS.md`.
## Task Tracking
@@ -69,6 +73,8 @@ For multi-step, cross-module, or interruption-prone work, maintain the repositor
first, then search the mapped active topics before scanning the broader public area.
- If the current branch and the mapped active topics describe the same feature area, prefer resuming those topics first.
- If the repository state suggests in-flight work but no recovery document matches, reconstruct the safest next step from code, tests, and Git state before asking the user for clarification.
+- If the current turn already carries heavy recovery context, broad diffs, or long validation output, prefer a
+ recovery-point update and a clean stop over starting another large slice just because the code task itself remains open.
## Example Triggers
diff --git a/GFramework.Core/Ioc/MicrosoftDiContainer.cs b/GFramework.Core/Ioc/MicrosoftDiContainer.cs
index b9896950..461487c2 100644
--- a/GFramework.Core/Ioc/MicrosoftDiContainer.cs
+++ b/GFramework.Core/Ioc/MicrosoftDiContainer.cs
@@ -185,6 +185,12 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
///
private IServiceProvider? _provider;
+ ///
+ /// 冻结后可复用的服务类型可见性索引。
+ /// 容器冻结后注册集合不再变化,因此 可以安全复用该索引。
+ ///
+ private FrozenServiceTypeIndex? _frozenServiceTypeIndex;
+
///
/// 容器冻结状态标志,true表示容器已冻结不可修改
///
@@ -1044,6 +1050,11 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
EnterReadLockOrThrowDisposed();
try
{
+ if (_frozenServiceTypeIndex is not null)
+ {
+ return _frozenServiceTypeIndex.Contains(type);
+ }
+
return HasRegistrationCore(type);
}
finally
@@ -1139,6 +1150,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
GetServicesUnsafe.Clear();
_registeredInstances.Clear();
_provider = null;
+ _frozenServiceTypeIndex = null;
_frozen = false;
_logger.Info("Container cleared");
}
@@ -1166,6 +1178,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
}
_provider = GetServicesUnsafe.BuildServiceProvider();
+ _frozenServiceTypeIndex = FrozenServiceTypeIndex.Create(GetServicesUnsafe);
_frozen = true;
_logger.Info("IOC Container frozen - ServiceProvider built");
}
@@ -1175,6 +1188,59 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
}
}
+ ///
+ /// 保存冻结后按服务键可见的精确服务类型与开放泛型定义集合。
+ ///
+ ///
+ /// 该索引只回答“按当前服务键语义是否可见”,因此与 /
+ /// 一样不会退化为更宽松的可赋值匹配。
+ ///
+ private sealed class FrozenServiceTypeIndex(HashSet exactServiceTypes, HashSet openGenericServiceTypes)
+ {
+ private readonly HashSet _exactServiceTypes = exactServiceTypes;
+ private readonly HashSet _openGenericServiceTypes = openGenericServiceTypes;
+
+ ///
+ /// 基于冻结时最终确定的服务描述符集合创建索引。
+ ///
+ /// 冻结时的服务描述符序列。
+ /// 供存在性判断热路径复用的服务键索引。
+ public static FrozenServiceTypeIndex Create(IEnumerable descriptors)
+ {
+ ArgumentNullException.ThrowIfNull(descriptors);
+
+ var exactServiceTypes = new HashSet();
+ var openGenericServiceTypes = new HashSet();
+
+ foreach (var descriptor in descriptors)
+ {
+ var serviceType = descriptor.ServiceType;
+ exactServiceTypes.Add(serviceType);
+
+ if (serviceType.IsGenericTypeDefinition)
+ {
+ openGenericServiceTypes.Add(serviceType);
+ }
+ }
+
+ return new FrozenServiceTypeIndex(exactServiceTypes, openGenericServiceTypes);
+ }
+
+ ///
+ /// 判断当前索引是否声明了目标服务键。
+ ///
+ /// 要检查的服务类型。
+ /// 命中精确服务键或可闭合的开放泛型服务键时返回 。
+ public bool Contains(Type requestedType)
+ {
+ ArgumentNullException.ThrowIfNull(requestedType);
+
+ return _exactServiceTypes.Contains(requestedType) ||
+ requestedType.IsConstructedGenericType &&
+ _openGenericServiceTypes.Contains(requestedType.GetGenericTypeDefinition());
+ }
+ }
+
///
/// 获取底层的服务集合
/// 提供对内部IServiceCollection的访问权限,用于高级配置和自定义操作
@@ -1250,6 +1316,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
_disposed = true;
(_provider as IDisposable)?.Dispose();
_provider = null;
+ _frozenServiceTypeIndex = null;
GetServicesUnsafe.Clear();
_registeredInstances.Clear();
_frozen = false;
diff --git a/GFramework.Cqrs.Benchmarks/Messaging/BenchmarkHostFactory.cs b/GFramework.Cqrs.Benchmarks/Messaging/BenchmarkHostFactory.cs
index 886f2c71..fb93cc32 100644
--- a/GFramework.Cqrs.Benchmarks/Messaging/BenchmarkHostFactory.cs
+++ b/GFramework.Cqrs.Benchmarks/Messaging/BenchmarkHostFactory.cs
@@ -3,10 +3,13 @@
using System;
using System.Linq;
+using GFramework.Core.Abstractions.Logging;
using GFramework.Core.Ioc;
using GFramework.Cqrs.Abstractions.Cqrs;
+using GFramework.Cqrs.Internal;
using MediatR;
using Microsoft.Extensions.DependencyInjection;
+using LegacyICqrsRuntime = GFramework.Core.Abstractions.Cqrs.ICqrsRuntime;
namespace GFramework.Cqrs.Benchmarks.Messaging;
@@ -31,11 +34,91 @@ internal static class BenchmarkHostFactory
ArgumentNullException.ThrowIfNull(configure);
var container = new MicrosoftDiContainer();
+ RegisterCqrsInfrastructure(container);
configure(container);
container.Freeze();
return container;
}
+ ///
+ /// 为 benchmark 宿主补齐默认 CQRS runtime seam,确保它既能手工注册 handler,也能走真实的程序集注册入口。
+ ///
+ /// 当前 benchmark 拥有的 GFramework 容器。
+ ///
+ /// `RegisterCqrsHandlersFromAssembly(...)` 依赖预先可见的 runtime / registrar / registration service 实例绑定。
+ /// benchmark 宿主直接使用裸 ,因此需要在配置阶段先补齐这组基础设施,
+ /// 避免各个 benchmark 用例各自复制同一段前置接线逻辑。
+ ///
+ private static void RegisterCqrsInfrastructure(MicrosoftDiContainer container)
+ {
+ ArgumentNullException.ThrowIfNull(container);
+
+ if (container.Get() is null)
+ {
+ var runtimeLogger = LoggerFactoryResolver.Provider.CreateLogger("CqrsDispatcher");
+ var notificationPublisher = container.Get();
+ var runtime = GFramework.Cqrs.CqrsRuntimeFactory.CreateRuntime(container, runtimeLogger, notificationPublisher);
+ container.Register(runtime);
+ RegisterLegacyRuntimeAlias(container, runtime);
+ }
+ else if (container.Get() is null)
+ {
+ RegisterLegacyRuntimeAlias(container, container.GetRequired());
+ }
+
+ if (container.Get() is null)
+ {
+ var registrarLogger = LoggerFactoryResolver.Provider.CreateLogger("DefaultCqrsHandlerRegistrar");
+ var registrar = GFramework.Cqrs.CqrsRuntimeFactory.CreateHandlerRegistrar(container, registrarLogger);
+ container.Register(registrar);
+ }
+
+ if (container.Get() is null)
+ {
+ var registrationLogger = LoggerFactoryResolver.Provider.CreateLogger("DefaultCqrsRegistrationService");
+ var registrar = container.GetRequired();
+ var registrationService = GFramework.Cqrs.CqrsRuntimeFactory.CreateRegistrationService(registrar, registrationLogger);
+ container.Register(registrationService);
+ }
+ }
+
+ ///
+ /// 只激活当前 benchmark 场景明确拥有的 generated registry,避免同一程序集里的其他 benchmark registry
+ /// 扩大冻结后服务索引与 dispatcher descriptor 基线。
+ ///
+ /// 当前 benchmark 需要接入的 generated registry 类型。
+ /// 承载 generated registry 注册结果的 GFramework benchmark 容器。
+ internal static void RegisterGeneratedBenchmarkRegistry(MicrosoftDiContainer container)
+ where TRegistry : class, GFramework.Cqrs.ICqrsHandlerRegistry
+ {
+ ArgumentNullException.ThrowIfNull(container);
+
+ var registrarLogger = LoggerFactoryResolver.Provider.CreateLogger("DefaultCqrsHandlerRegistrar");
+ CqrsHandlerRegistrar.RegisterGeneratedRegistry(container, typeof(TRegistry), registrarLogger);
+ }
+
+ ///
+ /// 为旧命名空间下的 CQRS runtime 契约注册兼容别名。
+ ///
+ /// 承载 runtime 别名的 benchmark 容器。
+ /// 当前正式 CQRS runtime 实例。
+ ///
+ /// 未同时实现 legacy CQRS runtime 契约。
+ ///
+ private static void RegisterLegacyRuntimeAlias(MicrosoftDiContainer container, ICqrsRuntime runtime)
+ {
+ ArgumentNullException.ThrowIfNull(container);
+ ArgumentNullException.ThrowIfNull(runtime);
+
+ if (runtime is not LegacyICqrsRuntime legacyRuntime)
+ {
+ throw new InvalidOperationException(
+ $"The registered {typeof(ICqrsRuntime).FullName} must also implement {typeof(LegacyICqrsRuntime).FullName}. Actual runtime type: {runtime.GetType().FullName}.");
+ }
+
+ container.Register(legacyRuntime);
+ }
+
///
/// 创建只承载当前 benchmark handler 集合的最小 MediatR 宿主。
///
diff --git a/GFramework.Cqrs.Benchmarks/Messaging/GeneratedDefaultRequestBenchmarkRegistry.cs b/GFramework.Cqrs.Benchmarks/Messaging/GeneratedDefaultRequestBenchmarkRegistry.cs
new file mode 100644
index 00000000..59646dc1
--- /dev/null
+++ b/GFramework.Cqrs.Benchmarks/Messaging/GeneratedDefaultRequestBenchmarkRegistry.cs
@@ -0,0 +1,100 @@
+// Copyright (c) 2025-2026 GeWuYou
+// SPDX-License-Identifier: Apache-2.0
+
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Threading;
+using System.Threading.Tasks;
+using GFramework.Core.Abstractions.Logging;
+using GFramework.Cqrs.Abstractions.Cqrs;
+using Microsoft.Extensions.DependencyInjection;
+
+[assembly: GFramework.Cqrs.CqrsHandlerRegistryAttribute(
+ typeof(GFramework.Cqrs.Benchmarks.Messaging.GeneratedDefaultRequestBenchmarkRegistry))]
+
+namespace GFramework.Cqrs.Benchmarks.Messaging;
+
+///
+/// 为默认 request steady-state benchmark 提供 hand-written generated registry,
+/// 以便验证“默认宿主吸收 generated request invoker provider”后的热路径收益。
+///
+public sealed class GeneratedDefaultRequestBenchmarkRegistry :
+ GFramework.Cqrs.ICqrsHandlerRegistry,
+ GFramework.Cqrs.ICqrsRequestInvokerProvider,
+ GFramework.Cqrs.IEnumeratesCqrsRequestInvokerDescriptors
+{
+ private static readonly GFramework.Cqrs.CqrsRequestInvokerDescriptor Descriptor =
+ new(
+ typeof(IRequestHandler<
+ RequestBenchmarks.BenchmarkRequest,
+ RequestBenchmarks.BenchmarkResponse>),
+ typeof(GeneratedDefaultRequestBenchmarkRegistry).GetMethod(
+ nameof(InvokeBenchmarkRequestHandler),
+ BindingFlags.Public | BindingFlags.Static)
+ ?? throw new InvalidOperationException("Missing generated default request benchmark method."));
+
+ private static readonly IReadOnlyList Descriptors =
+ [
+ new GFramework.Cqrs.CqrsRequestInvokerDescriptorEntry(
+ typeof(RequestBenchmarks.BenchmarkRequest),
+ typeof(RequestBenchmarks.BenchmarkResponse),
+ Descriptor)
+ ];
+
+ ///
+ /// 把默认 request benchmark handler 注册为单例,保持与原先 steady-state 宿主一致的生命周期语义。
+ ///
+ public void Register(IServiceCollection services, ILogger logger)
+ {
+ ArgumentNullException.ThrowIfNull(services);
+ ArgumentNullException.ThrowIfNull(logger);
+
+ services.AddSingleton(
+ typeof(IRequestHandler),
+ typeof(RequestBenchmarks.BenchmarkRequestHandler));
+ logger.Debug("Registered generated default request benchmark handler.");
+ }
+
+ ///
+ /// 返回当前 provider 暴露的全部 generated request invoker 描述符。
+ ///
+ public IReadOnlyList GetDescriptors()
+ {
+ return Descriptors;
+ }
+
+ ///
+ /// 为目标请求/响应类型对返回 generated request invoker 描述符。
+ ///
+ public bool TryGetDescriptor(
+ Type requestType,
+ Type responseType,
+ out GFramework.Cqrs.CqrsRequestInvokerDescriptor? descriptor)
+ {
+ if (requestType == typeof(RequestBenchmarks.BenchmarkRequest) &&
+ responseType == typeof(RequestBenchmarks.BenchmarkResponse))
+ {
+ descriptor = Descriptor;
+ return true;
+ }
+
+ descriptor = null;
+ return false;
+ }
+
+ ///
+ /// 模拟 generated invoker provider 为默认 request benchmark 产出的开放静态调用入口。
+ ///
+ public static ValueTask InvokeBenchmarkRequestHandler(
+ object handler,
+ object request,
+ CancellationToken cancellationToken)
+ {
+ var typedHandler = (IRequestHandler<
+ RequestBenchmarks.BenchmarkRequest,
+ RequestBenchmarks.BenchmarkResponse>)handler;
+ var typedRequest = (RequestBenchmarks.BenchmarkRequest)request;
+ return typedHandler.Handle(typedRequest, cancellationToken);
+ }
+}
diff --git a/GFramework.Cqrs.Benchmarks/Messaging/GeneratedDefaultStreamingBenchmarkRegistry.cs b/GFramework.Cqrs.Benchmarks/Messaging/GeneratedDefaultStreamingBenchmarkRegistry.cs
new file mode 100644
index 00000000..57a1b9a2
--- /dev/null
+++ b/GFramework.Cqrs.Benchmarks/Messaging/GeneratedDefaultStreamingBenchmarkRegistry.cs
@@ -0,0 +1,96 @@
+// Copyright (c) 2025-2026 GeWuYou
+// SPDX-License-Identifier: Apache-2.0
+
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Threading;
+using GFramework.Core.Abstractions.Logging;
+using GFramework.Cqrs.Abstractions.Cqrs;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace GFramework.Cqrs.Benchmarks.Messaging;
+
+///
+/// 为默认 stream steady-state benchmark 提供 hand-written generated registry,
+/// 以便验证“默认 stream 宿主吸收 generated stream invoker provider”后的完整枚举收益。
+///
+public sealed class GeneratedDefaultStreamingBenchmarkRegistry :
+ GFramework.Cqrs.ICqrsHandlerRegistry,
+ GFramework.Cqrs.ICqrsStreamInvokerProvider,
+ GFramework.Cqrs.IEnumeratesCqrsStreamInvokerDescriptors
+{
+ private static readonly GFramework.Cqrs.CqrsStreamInvokerDescriptor Descriptor =
+ new(
+ typeof(IStreamRequestHandler<
+ StreamingBenchmarks.BenchmarkStreamRequest,
+ StreamingBenchmarks.BenchmarkResponse>),
+ typeof(GeneratedDefaultStreamingBenchmarkRegistry).GetMethod(
+ nameof(InvokeBenchmarkStreamHandler),
+ BindingFlags.Public | BindingFlags.Static)
+ ?? throw new InvalidOperationException("Missing generated default streaming benchmark method."));
+
+ private static readonly IReadOnlyList Descriptors =
+ [
+ new GFramework.Cqrs.CqrsStreamInvokerDescriptorEntry(
+ typeof(StreamingBenchmarks.BenchmarkStreamRequest),
+ typeof(StreamingBenchmarks.BenchmarkResponse),
+ Descriptor)
+ ];
+
+ ///
+ /// 把默认 stream benchmark handler 注册为单例,保持与原先 steady-state 宿主一致的生命周期语义。
+ ///
+ public void Register(IServiceCollection services, ILogger logger)
+ {
+ ArgumentNullException.ThrowIfNull(services);
+ ArgumentNullException.ThrowIfNull(logger);
+
+ services.AddSingleton(
+ typeof(IStreamRequestHandler),
+ typeof(StreamingBenchmarks.BenchmarkStreamHandler));
+ logger.Debug("Registered generated default streaming benchmark handler.");
+ }
+
+ ///
+ /// 返回当前 provider 暴露的全部 generated stream invoker 描述符。
+ ///
+ public IReadOnlyList GetDescriptors()
+ {
+ return Descriptors;
+ }
+
+ ///
+ /// 为目标流式请求/响应类型对返回 generated stream invoker 描述符。
+ ///
+ public bool TryGetDescriptor(
+ Type requestType,
+ Type responseType,
+ out GFramework.Cqrs.CqrsStreamInvokerDescriptor? descriptor)
+ {
+ if (requestType == typeof(StreamingBenchmarks.BenchmarkStreamRequest) &&
+ responseType == typeof(StreamingBenchmarks.BenchmarkResponse))
+ {
+ descriptor = Descriptor;
+ return true;
+ }
+
+ descriptor = null;
+ return false;
+ }
+
+ ///
+ /// 模拟 generated stream invoker provider 为默认 stream benchmark 产出的开放静态调用入口。
+ ///
+ public static object InvokeBenchmarkStreamHandler(
+ object handler,
+ object request,
+ CancellationToken cancellationToken)
+ {
+ var typedHandler = (IStreamRequestHandler<
+ StreamingBenchmarks.BenchmarkStreamRequest,
+ StreamingBenchmarks.BenchmarkResponse>)handler;
+ var typedRequest = (StreamingBenchmarks.BenchmarkStreamRequest)request;
+ return typedHandler.Handle(typedRequest, cancellationToken);
+ }
+}
diff --git a/GFramework.Cqrs.Benchmarks/Messaging/GeneratedRequestPipelineBenchmarkRegistry.cs b/GFramework.Cqrs.Benchmarks/Messaging/GeneratedRequestPipelineBenchmarkRegistry.cs
new file mode 100644
index 00000000..5844cee1
--- /dev/null
+++ b/GFramework.Cqrs.Benchmarks/Messaging/GeneratedRequestPipelineBenchmarkRegistry.cs
@@ -0,0 +1,100 @@
+// Copyright (c) 2025-2026 GeWuYou
+// SPDX-License-Identifier: Apache-2.0
+
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Threading;
+using System.Threading.Tasks;
+using GFramework.Core.Abstractions.Logging;
+using GFramework.Cqrs.Abstractions.Cqrs;
+using Microsoft.Extensions.DependencyInjection;
+
+[assembly: GFramework.Cqrs.CqrsHandlerRegistryAttribute(
+ typeof(GFramework.Cqrs.Benchmarks.Messaging.GeneratedRequestPipelineBenchmarkRegistry))]
+
+namespace GFramework.Cqrs.Benchmarks.Messaging;
+
+///
+/// 为 request pipeline benchmark 提供 handwritten generated registry,
+/// 让默认 pipeline 宿主也能走真实的 generated request invoker provider 接线路径。
+///
+public sealed class GeneratedRequestPipelineBenchmarkRegistry :
+ GFramework.Cqrs.ICqrsHandlerRegistry,
+ GFramework.Cqrs.ICqrsRequestInvokerProvider,
+ GFramework.Cqrs.IEnumeratesCqrsRequestInvokerDescriptors
+{
+ private static readonly GFramework.Cqrs.CqrsRequestInvokerDescriptor Descriptor =
+ new(
+ typeof(IRequestHandler<
+ RequestPipelineBenchmarks.BenchmarkRequest,
+ RequestPipelineBenchmarks.BenchmarkResponse>),
+ typeof(GeneratedRequestPipelineBenchmarkRegistry).GetMethod(
+ nameof(InvokeBenchmarkRequestHandler),
+ BindingFlags.Public | BindingFlags.Static)
+ ?? throw new InvalidOperationException("Missing generated request pipeline benchmark method."));
+
+ private static readonly IReadOnlyList Descriptors =
+ [
+ new GFramework.Cqrs.CqrsRequestInvokerDescriptorEntry(
+ typeof(RequestPipelineBenchmarks.BenchmarkRequest),
+ typeof(RequestPipelineBenchmarks.BenchmarkResponse),
+ Descriptor)
+ ];
+
+ ///
+ /// 将 request pipeline benchmark handler 注册为单例,保持与当前矩阵宿主一致的生命周期语义。
+ ///
+ public void Register(IServiceCollection services, ILogger logger)
+ {
+ ArgumentNullException.ThrowIfNull(services);
+ ArgumentNullException.ThrowIfNull(logger);
+
+ services.AddSingleton(
+ typeof(IRequestHandler),
+ typeof(RequestPipelineBenchmarks.BenchmarkRequestHandler));
+ logger.Debug("Registered generated request pipeline benchmark handler.");
+ }
+
+ ///
+ /// 返回当前 provider 暴露的全部 generated request invoker 描述符。
+ ///
+ public IReadOnlyList GetDescriptors()
+ {
+ return Descriptors;
+ }
+
+ ///
+ /// 为目标请求/响应类型对返回 generated request invoker 描述符。
+ ///
+ public bool TryGetDescriptor(
+ Type requestType,
+ Type responseType,
+ out GFramework.Cqrs.CqrsRequestInvokerDescriptor? descriptor)
+ {
+ if (requestType == typeof(RequestPipelineBenchmarks.BenchmarkRequest) &&
+ responseType == typeof(RequestPipelineBenchmarks.BenchmarkResponse))
+ {
+ descriptor = Descriptor;
+ return true;
+ }
+
+ descriptor = null;
+ return false;
+ }
+
+ ///
+ /// 模拟 generated invoker provider 为 request pipeline benchmark 产出的开放静态调用入口。
+ ///
+ public static ValueTask InvokeBenchmarkRequestHandler(
+ object handler,
+ object request,
+ CancellationToken cancellationToken)
+ {
+ var typedHandler = (IRequestHandler<
+ RequestPipelineBenchmarks.BenchmarkRequest,
+ RequestPipelineBenchmarks.BenchmarkResponse>)handler;
+ var typedRequest = (RequestPipelineBenchmarks.BenchmarkRequest)request;
+ return typedHandler.Handle(typedRequest, cancellationToken);
+ }
+}
diff --git a/GFramework.Cqrs.Benchmarks/Messaging/GeneratedStreamLifetimeBenchmarkRegistry.cs b/GFramework.Cqrs.Benchmarks/Messaging/GeneratedStreamLifetimeBenchmarkRegistry.cs
new file mode 100644
index 00000000..977a14a5
--- /dev/null
+++ b/GFramework.Cqrs.Benchmarks/Messaging/GeneratedStreamLifetimeBenchmarkRegistry.cs
@@ -0,0 +1,110 @@
+// Copyright (c) 2025-2026 GeWuYou
+// SPDX-License-Identifier: Apache-2.0
+
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Threading;
+using GFramework.Core.Abstractions.Logging;
+using GFramework.Cqrs.Abstractions.Cqrs;
+using Microsoft.Extensions.DependencyInjection;
+
+[assembly: GFramework.Cqrs.CqrsHandlerRegistryAttribute(
+ typeof(GFramework.Cqrs.Benchmarks.Messaging.GeneratedStreamLifetimeBenchmarkRegistry))]
+
+namespace GFramework.Cqrs.Benchmarks.Messaging;
+
+///
+/// 为 stream 生命周期矩阵 benchmark 提供 hand-written generated registry,
+/// 以便在默认 generated-provider 宿主路径上比较不同 handler 生命周期的完整枚举成本。
+///
+public sealed class GeneratedStreamLifetimeBenchmarkRegistry :
+ GFramework.Cqrs.ICqrsHandlerRegistry,
+ GFramework.Cqrs.ICqrsStreamInvokerProvider,
+ GFramework.Cqrs.IEnumeratesCqrsStreamInvokerDescriptors
+{
+ private static readonly GFramework.Cqrs.CqrsStreamInvokerDescriptor Descriptor =
+ new(
+ typeof(IStreamRequestHandler<
+ StreamLifetimeBenchmarks.BenchmarkStreamRequest,
+ StreamLifetimeBenchmarks.BenchmarkResponse>),
+ typeof(GeneratedStreamLifetimeBenchmarkRegistry).GetMethod(
+ nameof(InvokeBenchmarkStreamHandler),
+ BindingFlags.Public | BindingFlags.Static)
+ ?? throw new InvalidOperationException("Missing generated stream lifetime benchmark method."));
+
+ private static readonly IReadOnlyList Descriptors =
+ [
+ new GFramework.Cqrs.CqrsStreamInvokerDescriptorEntry(
+ typeof(StreamLifetimeBenchmarks.BenchmarkStreamRequest),
+ typeof(StreamLifetimeBenchmarks.BenchmarkResponse),
+ Descriptor)
+ ];
+
+ ///
+ /// 参与程序集注册入口,但不在这里直接写入 handler 生命周期。
+ ///
+ /// 当前 generated registry 拥有的服务集合。
+ /// 用于记录 generated registry 注册行为的日志器。
+ ///
+ /// 生命周期矩阵需要让 benchmark 主体显式控制 `Singleton / Transient` 变量。
+ /// 因此 registry 只负责暴露 generated descriptor,不在这里抢先注册 handler,避免把默认单例注册混入比较结果。
+ ///
+ public void Register(IServiceCollection services, ILogger logger)
+ {
+ ArgumentNullException.ThrowIfNull(services);
+ ArgumentNullException.ThrowIfNull(logger);
+
+ logger.Debug("Registered generated stream lifetime benchmark descriptors.");
+ }
+
+ ///
+ /// 返回当前 provider 暴露的全部 generated stream invoker 描述符。
+ ///
+ public IReadOnlyList GetDescriptors()
+ {
+ return Descriptors;
+ }
+
+ ///
+ /// 为目标流式请求/响应类型对返回 generated stream invoker 描述符。
+ ///
+ /// 待匹配的请求类型。
+ /// 待匹配的响应类型。
+ /// 命中时返回的 generated descriptor。
+ /// 命中当前 benchmark 的请求/响应类型对时返回 。
+ public bool TryGetDescriptor(
+ Type requestType,
+ Type responseType,
+ out GFramework.Cqrs.CqrsStreamInvokerDescriptor? descriptor)
+ {
+ if (requestType == typeof(StreamLifetimeBenchmarks.BenchmarkStreamRequest) &&
+ responseType == typeof(StreamLifetimeBenchmarks.BenchmarkResponse))
+ {
+ descriptor = Descriptor;
+ return true;
+ }
+
+ descriptor = null;
+ return false;
+ }
+
+ ///
+ /// 模拟 generated stream invoker provider 为生命周期矩阵 benchmark 产出的开放静态调用入口。
+ ///
+ /// 当前请求对应的 handler 实例。
+ /// 待分发的流式请求。
+ /// 调用方传入的取消令牌。
+ /// 交给目标 stream handler 处理后的异步枚举。
+ public static object InvokeBenchmarkStreamHandler(
+ object handler,
+ object request,
+ CancellationToken cancellationToken)
+ {
+ var typedHandler = (IStreamRequestHandler<
+ StreamLifetimeBenchmarks.BenchmarkStreamRequest,
+ StreamLifetimeBenchmarks.BenchmarkResponse>)handler;
+ var typedRequest = (StreamLifetimeBenchmarks.BenchmarkStreamRequest)request;
+ return typedHandler.Handle(typedRequest, cancellationToken);
+ }
+}
diff --git a/GFramework.Cqrs.Benchmarks/Messaging/RequestBenchmarks.cs b/GFramework.Cqrs.Benchmarks/Messaging/RequestBenchmarks.cs
index 37840a94..9d946e72 100644
--- a/GFramework.Cqrs.Benchmarks/Messaging/RequestBenchmarks.cs
+++ b/GFramework.Cqrs.Benchmarks/Messaging/RequestBenchmarks.cs
@@ -61,12 +61,12 @@ public class RequestBenchmarks
MinLevel = LogLevel.Fatal
};
Fixture.Setup("Request", handlerCount: 1, pipelineCount: 0);
+ BenchmarkDispatcherCacheHelper.ClearDispatcherCaches();
_baselineHandler = new BenchmarkRequestHandler();
_container = BenchmarkHostFactory.CreateFrozenGFrameworkContainer(container =>
{
- container.RegisterSingleton>(
- _baselineHandler);
+ BenchmarkHostFactory.RegisterGeneratedBenchmarkRegistry(container);
});
_runtime = GFramework.Cqrs.CqrsRuntimeFactory.CreateRuntime(
_container,
@@ -91,7 +91,14 @@ public class RequestBenchmarks
[GlobalCleanup]
public void Cleanup()
{
- BenchmarkCleanupHelper.DisposeAll(_container, _mediatrServiceProvider, _mediatorServiceProvider);
+ try
+ {
+ BenchmarkCleanupHelper.DisposeAll(_container, _mediatrServiceProvider, _mediatorServiceProvider);
+ }
+ finally
+ {
+ BenchmarkDispatcherCacheHelper.ClearDispatcherCaches();
+ }
}
///
diff --git a/GFramework.Cqrs.Benchmarks/Messaging/RequestInvokerBenchmarks.cs b/GFramework.Cqrs.Benchmarks/Messaging/RequestInvokerBenchmarks.cs
index 87e326e5..4b8589ed 100644
--- a/GFramework.Cqrs.Benchmarks/Messaging/RequestInvokerBenchmarks.cs
+++ b/GFramework.Cqrs.Benchmarks/Messaging/RequestInvokerBenchmarks.cs
@@ -83,7 +83,7 @@ public class RequestInvokerBenchmarks
_generatedContainer = BenchmarkHostFactory.CreateFrozenGFrameworkContainer(container =>
{
- container.RegisterCqrsHandlersFromAssembly(typeof(RequestInvokerBenchmarks).Assembly);
+ BenchmarkHostFactory.RegisterGeneratedBenchmarkRegistry(container);
});
_generatedRuntime = GFramework.Cqrs.CqrsRuntimeFactory.CreateRuntime(
_generatedContainer,
diff --git a/GFramework.Cqrs.Benchmarks/Messaging/RequestPipelineBenchmarks.cs b/GFramework.Cqrs.Benchmarks/Messaging/RequestPipelineBenchmarks.cs
index 07dbc0c5..a2883955 100644
--- a/GFramework.Cqrs.Benchmarks/Messaging/RequestPipelineBenchmarks.cs
+++ b/GFramework.Cqrs.Benchmarks/Messaging/RequestPipelineBenchmarks.cs
@@ -69,8 +69,7 @@ public class RequestPipelineBenchmarks
_baselineHandler = new BenchmarkRequestHandler();
_container = BenchmarkHostFactory.CreateFrozenGFrameworkContainer(container =>
{
- container.RegisterSingleton>(
- _baselineHandler);
+ BenchmarkHostFactory.RegisterGeneratedBenchmarkRegistry(container);
RegisterGFrameworkPipelineBehaviors(container, PipelineCount);
});
_runtime = GFramework.Cqrs.CqrsRuntimeFactory.CreateRuntime(
diff --git a/GFramework.Cqrs.Benchmarks/Messaging/StreamInvokerBenchmarks.cs b/GFramework.Cqrs.Benchmarks/Messaging/StreamInvokerBenchmarks.cs
index a552a233..deacac21 100644
--- a/GFramework.Cqrs.Benchmarks/Messaging/StreamInvokerBenchmarks.cs
+++ b/GFramework.Cqrs.Benchmarks/Messaging/StreamInvokerBenchmarks.cs
@@ -83,7 +83,7 @@ public class StreamInvokerBenchmarks
_generatedContainer = BenchmarkHostFactory.CreateFrozenGFrameworkContainer(container =>
{
- container.RegisterCqrsHandlersFromAssembly(typeof(StreamInvokerBenchmarks).Assembly);
+ BenchmarkHostFactory.RegisterGeneratedBenchmarkRegistry(container);
});
_generatedRuntime = GFramework.Cqrs.CqrsRuntimeFactory.CreateRuntime(
_generatedContainer,
diff --git a/GFramework.Cqrs.Benchmarks/Messaging/StreamLifetimeBenchmarks.cs b/GFramework.Cqrs.Benchmarks/Messaging/StreamLifetimeBenchmarks.cs
new file mode 100644
index 00000000..7d761d03
--- /dev/null
+++ b/GFramework.Cqrs.Benchmarks/Messaging/StreamLifetimeBenchmarks.cs
@@ -0,0 +1,279 @@
+// Copyright (c) 2025-2026 GeWuYou
+// SPDX-License-Identifier: Apache-2.0
+
+using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Columns;
+using BenchmarkDotNet.Configs;
+using BenchmarkDotNet.Diagnosers;
+using BenchmarkDotNet.Jobs;
+using BenchmarkDotNet.Order;
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using GFramework.Core.Abstractions.Logging;
+using GFramework.Core.Ioc;
+using GFramework.Core.Logging;
+using GFramework.Cqrs.Abstractions.Cqrs;
+using MediatR;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace GFramework.Cqrs.Benchmarks.Messaging;
+
+///
+/// 对比 stream 完整枚举在不同 handler 生命周期下的额外开销。
+///
+///
+/// 当前矩阵只覆盖 `Singleton` 与 `Transient`。
+/// `Scoped` 仍依赖真实的显式作用域边界;在当前“单根容器最小宿主”模型下直接加入 scoped 会把枚举宿主成本与生命周期成本混在一起,
+/// 因此保持与 request 生命周期矩阵相同的边界,留待后续 scoped host 基线具备后再扩展。
+///
+[Config(typeof(Config))]
+public class StreamLifetimeBenchmarks
+{
+ private MicrosoftDiContainer _container = null!;
+ private ICqrsRuntime _runtime = null!;
+ private ServiceProvider _serviceProvider = null!;
+ private IMediator _mediatr = null!;
+ private BenchmarkStreamHandler _baselineHandler = null!;
+ private BenchmarkStreamRequest _request = null!;
+
+ ///
+ /// 控制当前 benchmark 使用的 handler 生命周期。
+ ///
+ [Params(HandlerLifetime.Singleton, HandlerLifetime.Transient)]
+ public HandlerLifetime Lifetime { get; set; }
+
+ ///
+ /// 可公平比较的 benchmark handler 生命周期集合。
+ ///
+ public enum HandlerLifetime
+ {
+ ///
+ /// 复用单个 handler 实例。
+ ///
+ Singleton,
+
+ ///
+ /// 每次建流都重新解析新的 handler 实例。
+ ///
+ Transient
+ }
+
+ ///
+ /// 配置 stream 生命周期 benchmark 的公共输出格式。
+ ///
+ private sealed class Config : ManualConfig
+ {
+ public Config()
+ {
+ AddJob(Job.Default);
+ AddColumnProvider(DefaultColumnProviders.Instance);
+ AddColumn(new CustomColumn("Scenario", static (_, _) => "StreamLifetime"));
+ AddDiagnoser(MemoryDiagnoser.Default);
+ WithOrderer(new DefaultOrderer(SummaryOrderPolicy.FastestToSlowest, MethodOrderPolicy.Declared));
+ }
+ }
+
+ ///
+ /// 构建当前生命周期下的 GFramework 与 MediatR stream 对照宿主。
+ ///
+ [GlobalSetup]
+ public void Setup()
+ {
+ LoggerFactoryResolver.Provider = new ConsoleLoggerFactoryProvider
+ {
+ MinLevel = LogLevel.Fatal
+ };
+ Fixture.Setup($"StreamLifetime/{Lifetime}", handlerCount: 1, pipelineCount: 0);
+ BenchmarkDispatcherCacheHelper.ClearDispatcherCaches();
+
+ _baselineHandler = new BenchmarkStreamHandler();
+ _request = new BenchmarkStreamRequest(Guid.NewGuid(), 3);
+
+ _container = BenchmarkHostFactory.CreateFrozenGFrameworkContainer(container =>
+ {
+ BenchmarkHostFactory.RegisterGeneratedBenchmarkRegistry(container);
+ RegisterGFrameworkHandler(container, Lifetime);
+ });
+ // 容器内已提前保留默认 runtime 以支撑 generated registry 接线;
+ // 这里额外创建带生命周期后缀的 runtime,只是为了区分不同 benchmark 矩阵的 dispatcher 日志。
+ _runtime = GFramework.Cqrs.CqrsRuntimeFactory.CreateRuntime(
+ _container,
+ LoggerFactoryResolver.Provider.CreateLogger(nameof(StreamLifetimeBenchmarks) + "." + Lifetime));
+
+ _serviceProvider = BenchmarkHostFactory.CreateMediatRServiceProvider(
+ configure: null,
+ typeof(StreamLifetimeBenchmarks),
+ static candidateType => candidateType == typeof(BenchmarkStreamHandler),
+ ResolveMediatRLifetime(Lifetime));
+ _mediatr = _serviceProvider.GetRequiredService();
+ }
+
+ ///
+ /// 释放当前生命周期矩阵持有的 benchmark 宿主资源,并清理 dispatcher 缓存。
+ ///
+ [GlobalCleanup]
+ public void Cleanup()
+ {
+ try
+ {
+ BenchmarkCleanupHelper.DisposeAll(_container, _serviceProvider);
+ }
+ finally
+ {
+ BenchmarkDispatcherCacheHelper.ClearDispatcherCaches();
+ }
+ }
+
+ ///
+ /// 直接调用 handler 并完整枚举,作为不同生命周期矩阵下的 dispatch 额外开销 baseline。
+ ///
+ [Benchmark(Baseline = true)]
+ public async ValueTask Stream_Baseline()
+ {
+ await foreach (var response in _baselineHandler.Handle(_request, CancellationToken.None).ConfigureAwait(false))
+ {
+ _ = response;
+ }
+ }
+
+ ///
+ /// 通过 GFramework.CQRS runtime 创建并完整枚举 stream。
+ ///
+ [Benchmark]
+ public async ValueTask Stream_GFrameworkCqrs()
+ {
+ await foreach (var response in _runtime.CreateStream(BenchmarkContext.Instance, _request, CancellationToken.None)
+ .ConfigureAwait(false))
+ {
+ _ = response;
+ }
+ }
+
+ ///
+ /// 通过 MediatR 创建并完整枚举 stream,作为外部对照。
+ ///
+ [Benchmark]
+ public async ValueTask Stream_MediatR()
+ {
+ await foreach (var response in _mediatr.CreateStream(_request, CancellationToken.None).ConfigureAwait(false))
+ {
+ _ = response;
+ }
+ }
+
+ ///
+ /// 按生命周期把 benchmark stream handler 注册到 GFramework 容器。
+ ///
+ /// 当前 benchmark 拥有并负责释放的容器。
+ /// 待比较的 handler 生命周期。
+ ///
+ /// 先通过 generated registry 提供静态 descriptor,再显式覆盖 handler 生命周期,
+ /// 可以把比较变量收敛到 handler 解析成本,而不是 descriptor 发现路径本身。
+ ///
+ private static void RegisterGFrameworkHandler(MicrosoftDiContainer container, HandlerLifetime lifetime)
+ {
+ ArgumentNullException.ThrowIfNull(container);
+
+ switch (lifetime)
+ {
+ case HandlerLifetime.Singleton:
+ container.RegisterSingleton<
+ GFramework.Cqrs.Abstractions.Cqrs.IStreamRequestHandler,
+ BenchmarkStreamHandler>();
+ return;
+
+ case HandlerLifetime.Transient:
+ container.RegisterTransient<
+ GFramework.Cqrs.Abstractions.Cqrs.IStreamRequestHandler,
+ BenchmarkStreamHandler>();
+ return;
+
+ default:
+ throw new ArgumentOutOfRangeException(nameof(lifetime), lifetime, "Unsupported benchmark handler lifetime.");
+ }
+ }
+
+ ///
+ /// 将 benchmark 生命周期映射为 MediatR 组装所需的 。
+ ///
+ /// 待比较的 handler 生命周期。
+ /// 当前生命周期对应的 MediatR 注册方式。
+ private static ServiceLifetime ResolveMediatRLifetime(HandlerLifetime lifetime)
+ {
+ return lifetime switch
+ {
+ HandlerLifetime.Singleton => ServiceLifetime.Singleton,
+ HandlerLifetime.Transient => ServiceLifetime.Transient,
+ _ => throw new ArgumentOutOfRangeException(nameof(lifetime), lifetime, "Unsupported benchmark handler lifetime.")
+ };
+ }
+
+ ///
+ /// Benchmark stream request。
+ ///
+ /// 请求标识。
+ /// 返回元素数量。
+ public sealed record BenchmarkStreamRequest(Guid Id, int ItemCount) :
+ GFramework.Cqrs.Abstractions.Cqrs.IStreamRequest,
+ MediatR.IStreamRequest;
+
+ ///
+ /// Benchmark stream response。
+ ///
+ /// 响应标识。
+ public sealed record BenchmarkResponse(Guid Id);
+
+ ///
+ /// 同时实现 GFramework.CQRS 与 MediatR 契约的最小 stream handler。
+ ///
+ public sealed class BenchmarkStreamHandler :
+ GFramework.Cqrs.Abstractions.Cqrs.IStreamRequestHandler,
+ MediatR.IStreamRequestHandler
+ {
+ ///
+ /// 处理 GFramework.CQRS stream request。
+ ///
+ /// 当前 benchmark stream 请求。
+ /// 用于中断异步枚举的取消令牌。
+ /// 完整枚举所需的低噪声异步响应序列。
+ public IAsyncEnumerable Handle(
+ BenchmarkStreamRequest request,
+ CancellationToken cancellationToken)
+ {
+ return EnumerateAsync(request, cancellationToken);
+ }
+
+ ///
+ /// 处理 MediatR stream request。
+ ///
+ /// 当前 benchmark stream 请求。
+ /// 用于中断异步枚举的取消令牌。
+ /// 完整枚举所需的低噪声异步响应序列。
+ IAsyncEnumerable MediatR.IStreamRequestHandler.Handle(
+ BenchmarkStreamRequest request,
+ CancellationToken cancellationToken)
+ {
+ return EnumerateAsync(request, cancellationToken);
+ }
+
+ ///
+ /// 为生命周期矩阵构造稳定、低噪声的异步响应序列。
+ ///
+ /// 当前 benchmark 请求。
+ /// 用于中断异步枚举的取消令牌。
+ /// 按固定元素数量返回的异步响应序列。
+ private static async IAsyncEnumerable EnumerateAsync(
+ BenchmarkStreamRequest request,
+ [System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken)
+ {
+ for (var index = 0; index < request.ItemCount; index++)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ yield return new BenchmarkResponse(request.Id);
+ await Task.CompletedTask.ConfigureAwait(false);
+ }
+ }
+ }
+}
diff --git a/GFramework.Cqrs.Benchmarks/Messaging/StreamingBenchmarks.cs b/GFramework.Cqrs.Benchmarks/Messaging/StreamingBenchmarks.cs
index 8b3b1d94..8b886d29 100644
--- a/GFramework.Cqrs.Benchmarks/Messaging/StreamingBenchmarks.cs
+++ b/GFramework.Cqrs.Benchmarks/Messaging/StreamingBenchmarks.cs
@@ -18,6 +18,9 @@ using GFramework.Cqrs.Abstractions.Cqrs;
using MediatR;
using Microsoft.Extensions.DependencyInjection;
+[assembly: GFramework.Cqrs.CqrsHandlerRegistryAttribute(
+ typeof(GFramework.Cqrs.Benchmarks.Messaging.GeneratedDefaultStreamingBenchmarkRegistry))]
+
namespace GFramework.Cqrs.Benchmarks.Messaging;
///
@@ -59,12 +62,12 @@ public class StreamingBenchmarks
MinLevel = LogLevel.Fatal
};
Fixture.Setup("StreamRequest", handlerCount: 1, pipelineCount: 0);
+ BenchmarkDispatcherCacheHelper.ClearDispatcherCaches();
_baselineHandler = new BenchmarkStreamHandler();
_container = BenchmarkHostFactory.CreateFrozenGFrameworkContainer(container =>
{
- container.RegisterSingleton>(
- _baselineHandler);
+ BenchmarkHostFactory.RegisterGeneratedBenchmarkRegistry(container);
});
_runtime = GFramework.Cqrs.CqrsRuntimeFactory.CreateRuntime(
_container,
@@ -86,7 +89,14 @@ public class StreamingBenchmarks
[GlobalCleanup]
public void Cleanup()
{
- BenchmarkCleanupHelper.DisposeAll(_container, _serviceProvider);
+ try
+ {
+ BenchmarkCleanupHelper.DisposeAll(_container, _serviceProvider);
+ }
+ finally
+ {
+ BenchmarkDispatcherCacheHelper.ClearDispatcherCaches();
+ }
}
///
diff --git a/GFramework.Cqrs.Benchmarks/README.md b/GFramework.Cqrs.Benchmarks/README.md
index 9fc28cbd..f589a0e3 100644
--- a/GFramework.Cqrs.Benchmarks/README.md
+++ b/GFramework.Cqrs.Benchmarks/README.md
@@ -15,11 +15,13 @@
- `Messaging/Fixture.cs`
- 运行前输出并校验场景配置
- `Messaging/RequestBenchmarks.cs`
- - direct handler、NuGet `Mediator` source-generated concrete path、`GFramework.Cqrs` runtime 与 `MediatR` 的 request steady-state dispatch 对比
+ - direct handler、NuGet `Mediator` source-generated concrete path、已接上 handwritten generated request invoker provider 的默认 `GFramework.Cqrs` runtime 与 `MediatR` 的 request steady-state dispatch 对比
- `Messaging/RequestLifetimeBenchmarks.cs`
- `Singleton / Transient` 两类 handler 生命周期下,direct handler、`GFramework.Cqrs` runtime 与 `MediatR` 的 request steady-state dispatch 对比
+- `Messaging/StreamLifetimeBenchmarks.cs`
+ - `Singleton / Transient` 两类 handler 生命周期下,direct handler、已接上 handwritten generated stream invoker provider 的 `GFramework.Cqrs` runtime 与 `MediatR` 的 stream 完整枚举对比
- `Messaging/RequestPipelineBenchmarks.cs`
- - `0 / 1 / 4` 个 pipeline 行为下,direct handler、`GFramework.Cqrs` runtime 与 `MediatR` 的 request steady-state dispatch 对比
+ - `0 / 1 / 4` 个 pipeline 行为下,direct handler、已接上 handwritten generated request invoker provider 的 `GFramework.Cqrs` runtime 与 `MediatR` 的 request steady-state dispatch 对比
- `Messaging/RequestStartupBenchmarks.cs`
- `Initialization` 与 `ColdStart` 两组 request startup 成本对比,补齐与 `Mediator` comparison benchmark 更接近的 startup 维度
- `Messaging/RequestInvokerBenchmarks.cs`
@@ -29,7 +31,7 @@
- `Messaging/NotificationBenchmarks.cs`
- `GFramework.Cqrs` runtime 与 `MediatR` 的单处理器 notification publish 对比
- `Messaging/StreamingBenchmarks.cs`
- - direct handler、`GFramework.Cqrs` runtime 与 `MediatR` 的 stream request 完整枚举对比
+ - direct handler、已接上 handwritten generated stream invoker provider 的 `GFramework.Cqrs` runtime 与 `MediatR` 的 stream request 完整枚举对比
## 最小使用方式
@@ -51,6 +53,5 @@ dotnet run --project GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.cspro
- request / stream 的真实 source-generator 产物与 handwritten generated provider 对照
- `Mediator` 的 transient / scoped compile-time lifetime 矩阵对照
-- stream handler 生命周期矩阵
- 带真实显式作用域边界的 scoped host 对照
- generated invoker provider 与纯反射 dispatch / 建流对比继续扩展到更多场景
diff --git a/GFramework.Cqrs.Tests/Cqrs/CqrsDispatcherContextValidationTests.cs b/GFramework.Cqrs.Tests/Cqrs/CqrsDispatcherContextValidationTests.cs
index 5eec00eb..7452eb84 100644
--- a/GFramework.Cqrs.Tests/Cqrs/CqrsDispatcherContextValidationTests.cs
+++ b/GFramework.Cqrs.Tests/Cqrs/CqrsDispatcherContextValidationTests.cs
@@ -43,6 +43,63 @@ internal sealed class CqrsDispatcherContextValidationTests
Throws.InvalidOperationException.With.Message.Contains("does not implement IArchitectureContext"));
}
+ ///
+ /// 验证 request 上下文校验失败时,
+ /// 不会在调用点同步抛出,而是返回一个 faulted 保持既有异步失败语义。
+ ///
+ [Test]
+ public void SendAsync_Should_Return_Faulted_ValueTask_When_Context_Preparation_Fails()
+ {
+ var runtime = CreateRuntime(
+ container =>
+ {
+ container
+ .Setup(currentContainer => currentContainer.Get(typeof(IRequestHandler)))
+ .Returns(new ContextAwareRequestHandler());
+ container
+ .Setup(currentContainer => currentContainer.HasRegistration(typeof(IPipelineBehavior)))
+ .Returns(false);
+ });
+
+ ValueTask dispatch = default;
+ Assert.That(
+ () => { dispatch = runtime.SendAsync(new FakeCqrsContext(), new ContextAwareRequest()); },
+ Throws.Nothing);
+ Assert.That(
+ async () => await dispatch.ConfigureAwait(false),
+ Throws.InvalidOperationException.With.Message.Contains("does not implement IArchitectureContext"));
+ }
+
+ ///
+ /// 验证 request handler 缺失时,dispatcher 仍返回 faulted ,
+ /// 而不是在调用点同步抛出异常。
+ ///
+ [Test]
+ public void SendAsync_Should_Return_Faulted_ValueTask_When_Handler_Is_Missing()
+ {
+ var runtime = CreateRuntime(
+ container =>
+ {
+ container
+ .Setup(currentContainer => currentContainer.Get(typeof(IRequestHandler)))
+ .Returns((object?)null);
+ container
+ .Setup(currentContainer => currentContainer.HasRegistration(typeof(IPipelineBehavior)))
+ .Returns(false);
+ container
+ .Setup(currentContainer => currentContainer.GetAll(typeof(IPipelineBehavior)))
+ .Returns(Array.Empty