排查.NET 5迁移中EF Core 3.1的‘线程二次操作’错误原因
我们正在将旧.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强化了线程安全校验。
解决步骤
- 修改DbContext的DI作用域:将
IFHDbContext改为请求作用域注册,确保每个HTTP请求获取独立的上下文实例(以StructureMap为例):
// 替换原单例注册代码 For<IFHDbContext>().Use(() => new FHDbContext(DataSources.FHDataSource, entityNotifier)).RequestScoped();
(若使用其他DI容器,对应调整为请求作用域的注册语法,比如Autofac的InstancePerRequest)
检查关联服务的作用域:确认
_onlineQuoteParameterService和_splitNetworkService的注册作用域为请求作用域或瞬态,避免这些服务意外持有单例DbContext实例。排查异步操作阻塞:再次确认服务层的EF Core操作未使用同步阻塞调用(如
.Result、.Wait()),这类操作会导致上下文被绑定到非异步线程,加剧并发冲突风险。
内容的提问来源于stack exchange,提问作者geoff swartz

