DbContext并发操作异常求助:多线程复用实例引发InvalidOperationException
问题分析与解决方案
错误原因
这个InvalidOperationException的核心是同一个DbContext实例被同时用于多个未完成的操作,具体触发逻辑如下:
GetPictureForChat方法中通过_userManager.IsInRoleAsync发起了异步数据库操作(UserManager内部会复用应用的DbContext)- 紧接着在
GetListChats的循环里,调用了_unitOfWork.GenericRepository<Doctor>().GetAll(...).FirstOrDefault()这类同步数据库操作 - 异步操作尚未完成时,同步操作复用了同一个
DbContext,导致上下文被并发访问,触发线程安全冲突
修复步骤
1. 将Repository的同步查询改为异步版本
先确保IGenericRepository<T>和GenericRepository<T>实现异步查询方法:
// IGenericRepository<T> 接口新增 Task<T> FirstOrDefaultAsync(Expression<Func<T, bool>> predicate); // GenericRepository<T> 实现该方法 public async Task<T> FirstOrDefaultAsync(Expression<Func<T, bool>> predicate) { return await _context.Set<T>().FirstOrDefaultAsync(predicate); }
然后修改GetListChats中的同步调用为异步:
if (picture == null) { var doctor = await _unitOfWork.GenericRepository<Doctor>().FirstOrDefaultAsync(doctor => doctor.UserId == targetUser.Id); chat.Picture = doctor?.DoctorPicture; // 增加空值判断避免空引用异常 }
2. 统一使用异步数据库操作模式
禁止在同一个请求上下文内混合异步、同步数据库操作,所有涉及数据库的操作(包括Repository、UserManager调用)都必须使用异步方法并正确await。
3. 优化依赖注入生命周期(可选)
当前UnitOfWork注册为Transient,而DbContext默认是Scoped,同一个请求内的多个UnitOfWork实例会共享同一个DbContext。如果业务需要更严格的上下文隔离,可以将UnitOfWork改为Scoped:
builder.Services.AddScoped<IUnitOfWork, UnitOfWork>();
4. 精简GetPictureForChat的异步调用(可选)
合并两次IsInRoleAsync调用,减少异步操作次数:
private async Task<string> GetPictureForChat(User user) { var isAdmin = await _userManager.IsInRoleAsync(user, "Admin"); if (isAdmin) return "admin.png"; var isDoctor = await _userManager.IsInRoleAsync(user, "Doctor"); return isDoctor ? null : "user.png"; }
验证修复
修改完成后重新运行代码,异步操作会确保DbContext的操作按顺序执行,不会出现并发访问的情况,即可解决该异常。
内容的提问来源于stack exchange,提问作者Andrij Matviiv
相关产品推荐
相关产品推荐

