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

排查.NET 5迁移中EF Core 3.1的‘线程二次操作’错误原因

EF Core 3.1 并行AJAX请求上下文冲突问题排查

我们正在将旧.NET Framework 4.7.2 MVC应用迁移至.NET 5,部分项目设为NetStandard 2,数据层使用EF Core 3.1。页面加载时发起两个并行AJAX调用,其中一个报错:A second operation started on this context before a previous operation completed。已确认async/await配置正确,日志验证无其他线程占用该上下文,且EF6版本无此问题。原本预期DI会为每个独立AJAX请求提供新上下文,现附上相关代码并寻求错误定位方法。

JavaScript代码

const loadPerDiem = () => {
    return new Promise((resolve, reject) => {
        fetch("/Quote/PerDiemRates")
            .then(r => {
                let json = r.json();
                json.then(d => {
                    d.forEach((x) => {
                        var enumId = x.PerDiemEnumerationId;
                        var rate = x.Rate;

                        perDiemItems.forEach((y) => {
                            if (y.id == enumId)
                                y.rate = rate;
                        });
                    });
                    resolve(true);
                })
            });
    })
}

const getNetworks = () => {
    return new Promise((resolve, reject) => {
        fetch("/Quote/GetNetworks")
            .then(r => {
                r.json()
                    .then(d => {
                        networks = d;
                        resolve(true);
                    })
            });
    })
}

Promise.all([loadPerDiem(), getNetworks()])
        .then(r => {
            fetch("/QuoteAddOns/GetAllActive?v=" + new Date().getTime())
                .then(a => {
                    loadSavedQuotes();
                })
        })

接口代码

public async Task<ActionResult> PerDiemRates()
{
    return Json(await _onlineQuoteParameterService.PerDiemRatesAsync());
}

public async Task<JsonResult> GetNetworks()
{
    return Json(await _splitNetworkService.GetNetworksForDropDownAsync());
}

DI注册代码

public void Define()
{
    Scan(
        scan =>
        {
            scan.SingleImplementationsOfInterface();
            scan.TheCallingAssembly();

            scan.Assembly(ApplicationAssemblyName);
            foreach (var name in Assemblies)
                scan.Assembly(name);

            scan.AddAllTypesOf<IEventSubscriber>();
            scan.WithDefaultConventions();
        });

    var entityNotifier = new EntityUpdateNotifier(channelService, exchangeService);
    For<IFHDbContext>().Use(new FHDbContext(DataSources.FHDataSource, entityNotifier));

}

问题根源定位

从DI注册代码可直接看出核心问题:IFHDbContext被注册为单例实例(直接通过Use(new FHDbContext(...))创建并复用同一个对象),而非请求作用域。EF Core的DbContext设计为非线程安全,单例模式下多个并发AJAX请求会共享同一个上下文实例,当两个请求同时触发EF Core数据库操作时,就会触发上下文并发操作的报错。

EF6中未出现该问题,是因为旧MVC请求管道和EF6内部处理机制对并发冲突的检测更宽松,而EF Core强化了线程安全校验。

解决步骤

  1. 修改DbContext的DI作用域:将IFHDbContext改为请求作用域注册,确保每个HTTP请求获取独立的上下文实例(以StructureMap为例):
// 替换原单例注册代码
For<IFHDbContext>().Use(() => new FHDbContext(DataSources.FHDataSource, entityNotifier)).RequestScoped();

(若使用其他DI容器,对应调整为请求作用域的注册语法,比如Autofac的InstancePerRequest)

  1. 检查关联服务的作用域:确认_onlineQuoteParameterService和_splitNetworkService的注册作用域为请求作用域或瞬态,避免这些服务意外持有单例DbContext实例。

  2. 排查异步操作阻塞:再次确认服务层的EF Core操作未使用同步阻塞调用(如.Result、.Wait()),这类操作会导致上下文被绑定到非异步线程,加剧并发冲突风险。

内容的提问来源于stack exchange,提问作者geoff swartz

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 15:43:24