You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.Net Core 3.1中DelegatingHandler复用DbContext引发多线程冲突

解决方案:DbContext并发操作错误处理

问题根源

默认HttpClient在DI中是Singleton生命周期,你的自定义LtiUserApiAuthenticationHttpClientHandler会被绑定到这个Singleton实例上,因此Handler本身也会被当作Singleton对待。如果直接在Handler的构造函数中注入IUserService,该服务会被Handler长期持有,其生命周期被“提升”为Singleton——哪怕你的DbContext是Transient,只要IUserService变成Singleton,它内部的DbContext实例就会被多个并发请求共享,而EF Core的DbContext本身不是线程安全的,最终引发多线程操作同一个实例的错误。

正确解决步骤

1. 修改自定义Handler,通过IServiceScopeFactory创建作用域

在Handler中不要直接注入业务服务,而是注入IServiceScopeFactory,在SendAsync方法内部创建独立作用域,获取当前请求的Scoped服务实例:

public class LtiUserApiAuthenticationHttpClientHandler : DelegatingHandler
{
    private readonly IServiceScopeFactory _scopeFactory;

    public LtiUserApiAuthenticationHttpClientHandler(IServiceScopeFactory scopeFactory)
    {
        _scopeFactory = scopeFactory;
    }

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        // 创建独立作用域,保证每次请求获取到的服务是隔离的
        using var scope = _scopeFactory.CreateScope();
        var userService = scope.ServiceProvider.GetRequiredService<IUserService>();
        
        // 执行数据库查询
        var targetUser = await userService.Get(...);
        
        // 这里添加你的请求认证逻辑,比如设置Header等
        request.Headers.Add("Authorization", $"Bearer {targetUser.Token}");
        
        return await base.SendAsync(request, cancellationToken);
    }
}

2. 调整DI注册方式

  • 将Handler注册为Transient
  • 使用IHttpClientFactory注册你的HttpClient,绑定Handler:
// Startup.cs 或 Program.cs(.NET 6+)
services.AddTransient<LtiUserApiAuthenticationHttpClientHandler>();

services.AddHttpClient<ILtiUserApiClient, LtiUserApiClient>()
    .AddHttpMessageHandler<LtiUserApiAuthenticationHttpClientHandler>();

3. 确保业务服务和DbContext的生命周期配置正确

  • 将IUserService及其实现注册为Scoped(每个请求一个实例)
  • DbContext保持Transient或Scoped均可(推荐Scoped,更符合请求生命周期):
services.AddScoped<IUserService, UserService>();
services.AddDbContext<MyDbContext>(options => 
    options.UseSqlServer(Configuration.GetConnectionString("Default")), 
    ServiceLifetime.Scoped); // 显式指定Scoped,默认也是Scoped

4. 排查异步代码中的阻塞操作

确认IUserService.Get方法内所有EF Core操作都使用异步API并正确await,禁止使用.Result、.Wait()等阻塞调用——这类操作会破坏线程上下文,导致DbContext被意外共享。

验证

重新用ApacheBench做并发测试(20请求、并发2),此时每个请求的数据库查询都会在独立的作用域内使用专属的DbContext实例,不会出现多线程冲突问题。

内容的提问来源于stack exchange,提问作者Chris Rockwell

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 03:10:25