c# ASP.NET Core中的 IApplicationBuilder 和 IEndpointRouteBuilder 并发模型

13次阅读

IapplicationBuilder 和 IEndpointRouteBuilder 均非线程安全,仅限应用启动时顺序配置;运行时动态路由应使用 DynamicRouteValueTransformer 或自定义 EndpointDataSource,而非并发调用 map 方法。

c# ASP.NET Core中的 IApplicationBuilder 和 IEndpointRouteBuilder 并发模型

ASP.net Core 中 IApplicationBuilder 不是线程安全的,别在中间件里并发修改它

IApplicationBuilder 是构建请求处理管道的“装配线”,只在应用启动时(Startup.ConfigureProgram.csWebApplication 配置阶段)被顺序调用。它的 UseUseMiddlewareMap 等方法不是为运行时并发调用设计的。

  • 如果在某个中间件内部(比如异步任务中)偷偷调用 app.Use(...),会触发 InvalidOperationException:“Builder is already in use” 或 “The builder has already been built”
  • IApplicationBuilder 内部持有对 RequestDelegate 链的引用,且其 Build() 方法仅执行一次;重复或并发调用会破坏管道一致性
  • 常见误用场景:在健康检查中间件里动态注册新路由、在灰度中间件里根据 Header 注册临时终结点——这些都该移出 IApplicationBuilder 生命周期

IEndpointRouteBuilder 的线程安全性取决于具体实现,但 MapControllers() 等扩展方法不是运行时 API

IEndpointRouteBuilder(如 WebApplication 实现的 EndpointRouteBuilder)本身是线程不安全的,但它通常只在应用初始化阶段被使用。关键在于:所有 MapXxx() 方法(MapControllerRouteMapRazorPagesMapHub)都只是往内部路由表注册终结点描述符(EndpointDataSource),不涉及实时请求分发。

  • 这些注册操作必须在 app.MapXXX()endpoints.MapXXX() 调用期间完成,不能在 HttpContext.RequestServices 解析出的任意服务中调用
  • 若需运行时动态添加路由(例如插件系统),应通过自定义 EndpointDataSource + IEndpointRouteBuilder 的底层机制实现,而非直接调用 Map 方法
  • MapControllers() 本质是批量扫描程序集并生成 ControllerActionEndpointConventionBuilder,它不检查并发,但也不允许在 Build() 后再调用

真正支持运行时动态路由的是 DynamicRouteValueTransformerEndpointDataSource

如果你需要根据请求上下文(如租户 ID、Header、查询参数)改变路由行为,IApplicationBuilderIEndpointRouteBuilder 都不是目标接口。正确路径是:

  • DynamicRouteValueTransformer 在匹配后动态改写 RouteValueDictionary,它天然支持异步并发请求
  • 实现自定义 EndpointDataSource 并注入 IServiceCollection,配合后台定时刷新或事件驱动更新终结点集合(注意:更新时需保证 ChangeToken 通知消费者)
  • 避免在 Map 链中嵌套 app.Run(async ctx => { ... }) 并在里面调用 endpoints.MapGet —— 这会导致 InvalidOperationException: Cannot create child container from a resolved service
public class TenantRouteTransformer : DynamicRouteValueTransformer {     public override async ValueTask TransformAsync(HttpContext httpContext, RouteValueDictionary values)     {         var tenant = httpContext.Request.Headers["X-Tenant"].FirstOrDefault();         if (!string.IsNullOrEmpty(tenant))         {             values["tenant"] = tenant;         }         return values;     } }

并发模型的本质:ASP.NET Core 的请求处理是 per-request 的,但配置阶段是单次、顺序、不可重入的

整个框架把“配置”和“执行”严格分离:IApplicationBuilderIEndpointRouteBuilder 属于配置期对象,它们的生命周期止于 app.Build();之后所有并发请求都走同一个已构建好的 RequestDelegate 链,由 EndpointRoutingMiddlewareEndpointMiddleware 协同完成路由匹配与执行。

  • 不要试图在 HttpContext 中拿到 IApplicationBuilder 实例 —— 它根本不会被注入到 DI 容器
  • IEndpointRouteBuilder 也不是 Scoped 或 Transient 服务,它只作为 WebApplication 的内部属性存在,无法从 HttpContext.RequestServices 获取
  • 最易忽略的一点:即使你用 WebHostBuilder 手动构建多个 IWebHost,每个实例的 IApplicationBuilder 仍只在其自身启动阶段有效,跨实例共享毫无意义

text=ZqhQzanResources